深入浅出ES6

目录

let和const

块级作用域

不存在变量提升

不允许被重复声明

模板字符串

函数默认值,剩余参数,扩展运算符

函数默认值

剩余参数

扩展运算符

箭头函数

箭头函数的语法

对象中的函数和箭头函数

解构赋值

完全解构

不完全解构

重命名

对数组解构

对象的扩展

assign()方法

Symbol

Set和Map数据结构

Set

set转换为数组,数组去重

set实现字符串去重

Map

数组的扩展方法

from()将伪数组转换为真数组

of方法

copyWithin()方法

find()和findIndex()

keys()和values()和entries()

include()

迭代器Iterator

生成器Generator

Generator 与 Iterator 接口的关系

next方法的参数

for...of 循环

应用-异步操作同步化

Promise对象

Promise对象的基本使用

then()方法

catch()方法

all()方法

race()方法

async函数


ES6 入门教程https://es6.ruanyifeng.com/

let和const

块级作用域

let声明的变量,只在let命令所在的代码块内有效

          {
              var a = 10
              let b = 20
          }
          console.log(a)  // 10
          console.log(b)  // ReferenceError: b is not defined b未定义
          console.log(c)  // ReferenceError: c is not defined c未定义

不存在变量提升

          console.log(a)  // undefined
          var a = 10
          //var声明的变量会发生变量提升的现象,即变量可以在声明之前使用,值为undefined
  /*--------------------------------------------------------------------------------------------------------------*/
          console.log(b)  // ReferenceError: Cannot access 'b' before initialization
          let b = 20
          //let只允许变量先声明再使用,否则就会报错 在初始化之前无法访问'b'
  /*-------------------------------------------------------------------------------------------------------------*/   
          console.log(c)  // ReferenceError: Cannot access 'c' before initialization
          const c = 30
          //const也要求先声明再使用     

不允许被重复声明

          var a = 10
          var a = 20
          console.log(a)      // 20 ,var后声明的a覆盖掉了前面的a
  /*--------------------------------------------------------------------------------------------------------------*/
          let b = 30
          let b = 40
          console.log(b)     // SyntaxError: Identifier 'b' has already been declared
          // 语法错误,标识符'b'已经被声明了,let不允许重复声明
  /*--------------------------------------------------------------------------------------------------------------*/
          const c = 50
          const c = 60
          console.log(c)        // SyntaxError: Identifier 'c' has already been declared
          // 语法错误,标识符'c'已经被声明了,const不允许重复声明
  /*--------------------------------------------------------------------------------------------------------------*/
          function fn(age) {
              let age
          }                     // SyntaxError: Identifier 'age' has already been declared
          //let不允许在相同的作用域内重复声明一个变量
  /*--------------------------------------------------------------------------------------------------------------*/
          function fn(max) {
              const max
          }                     // SyntaxError: Identifier 'max' has already been declared
          //const不允许在相同的作用域下重复声明一个变量
  /*--------------------------------------------------------------------------------------------------------------*/
          const d = 10
          d = 20
          console.log(d)  // TypeError: Assignment to constant variable 
          // d 是一个赋值的常数变量,不允许被修改,const声明的变量不允许被修改
  /*--------------------------------------------------------------------------------------------------------------*/
          const obj = {
              uname: 'andy'
          }
          obj.uname = 'tom'
          console.log(obj)    // {uname: 'tom'}
          // 可以修改内部对象
          obj = {
              age: 18
          }
          console.log(obj)    // TypeError: Assignment to constant variable.
          //不允许直接修改对象
          const arr = []
          for (var i = 0; i < 10; i++) {
              arr[i] = function () {
                  return i
              }
          }
          console.log(arr);   //arr中存了十个函数
          console.log(arr[5]())   // 10
          // var 声明的变量有变量提升,执行完循环返回的i变成了10,在arr[5]()中的5改成任意一个数输出返回的都是10
  ​
          for (let i = 0; i < 10; i++) {
              arr[i] = function () {
                  return i
              }
          }
          console.log(arr);
          console.log(arr[5]())   //让索引号为5的函数执行,return的i是5
          let RegExp = 10
          console.log(RegExp)             // 10
          console.log(window.RegExp)      // ƒ RegExp() { [native code] }
          //let声明的变量不会造成变量污染影响到全局变量

模板字符串

以前的写法

      <div class="box"></div>
      <script>
          let div = document.querySelector('.box')
          let id = 1, uname = '小马哥'
          div.innerHTML = "<ul><li id=" + id + ">" + uname + "</li></ul>"

模板字符串写法

      <div class="box"></div>
      <script>
          let div = document.querySelector('.box')
          let id = 1, uname = '小马哥'
          let htmlStr = `
          <ul>
              <li id="${id}">${uname}</li>
          </ul>
          `
          div.innerHTML = htmlStr

函数默认值,剩余参数,扩展运算符

函数默认值

  //一个求和函数,赋予参数默认值
          function add(a, b) {
              a = a || 10
              b = b || 20
              console.log(a + b)
          }
          add()   // 30
  //当调用函数没有传递参数时,使用默认值计算,若传递有参数,则使用传参计算

在ES6中可以这样写

          function add(a = 10, b = 40) {
              console.log(a + b)
          }
          add()   //50

但是在附默认值,传参时这种写法是错误的

          function add(a = 10, b) {
              console.log(a + b)
          }
          add(20)     // NaN
  //此时20会被赋给a,b未赋值是undefined,两者相加不是一个数字,输出NaN

这个默认值也可以是一个函数

          function add(a = 10, b = getVal(5)) {
              console.log(a + b)
          }
          function getVal(val) {
              return val + 5
          }
          add()   // 20

剩余参数

在ES5中我们在不确定用户会传递几个参数时,我们会使用arguments来接收成一个伪数组

          let obj = {
              uname: '刘德华',
              age: 18,
              sex: '男'
          }
          let obj2 = {
              uname: 'andy',
              age: 20,
              sex: '女'
          }
          function pick(data) {
              console.log(arguments);     
              //arguments是pick里面的所有参数,伪数组的形式[{uname: '刘德华', age: 18, sex: '男'},'uname', 'sex']       
              let result = {}
              for (let i = 1; i < arguments.length; i++) {
                  console.log(arguments[i])   
                  //arguments[i]是属性名,arguments[1]是传递参数里面的uname,arguments[2]是传递参数里面的sex
                  result[arguments[i]] = data[arguments[i]]   //data[arguments[i]]的值分别是刘德华,男
                  //也就是说在这个循环里面给result加上属性uname,sex并分别赋上相应的值
                  console.log(result[arguments[i]])   //s被附了data[arguments[i]]的值
  ​
              }
              return result
          }
          let Data = pick(obj, 'uname', 'sex')   //前面这个参数是选择获取那个对象的值进行传参
          console.log(Data)   //  {uname: '刘德华', sex: '男'}

在ES6中我们可以获取一个真正的数组。使用(…+名字)这种语法来做形参,返回的是一个数组,但是这个一定要写在形参的最后一个

          function pick2(data, ...keys) {
              console.log(keys)       // ['uname', 'sex']
              let result = {}
              for (let i = 0; i < keys.length; i++) {
                  result[keys[i]] = data[keys[i]]
              }
              return result
          }
          let Data2 = pick2(obj, 'uname', 'sex')
          console.log(Data2)   // {uname: '刘德华', sex: '男'}

对比一下两者

          function checkArgs1(data) {
              console.log(arguments)
          }
          function checkArgs2(...args) {
              console.log(args);
          }
  //由此可见使用ES6提供新方法获得的数据不再是伪数组,可以使用相应的数组方法操作数据

控制台打印输出

扩展运算符

扩展运算符:将一个数组(或对象)分割,并将数组的各个项作为分离的参数传给函数。其实就是把数组或对象拆开

  //当要获取数组的最大值,以前我们会使用apply
          const arr = [12, 95, 46, 77, 182, 65, 42]
          console.log(Math.max.apply(window, arr));       // 182

在ES6中扩展运算符可以将其改写为

       console.log(Math.max(...arr)); //182

也可以将对象中的各个项作为分离的参数来传递

          let obj1 = { x: 100, y: 200 }
          let obj2 = {
              a: 250,
              ...obj1
          }
          console.log(obj2)   //  {a: 250, x: 100, y: 200}

箭头函数

箭头函数的语法

在ES5中我们这样定义一个函数

          function add(a, b) {
              return a + b
          }
          console.log(add(10, 20))    // 30

在ES6中我们可以使用箭头函数简化写法

          let add = (a, b) => {
              return a + b
          }
          console.log(add(5, 3))      // 8

像这种只有一个返回值的我们甚至可以有更简洁的写法

          let add = (a, b) => a + b
          console.log(add(5, 3))      // 8

当我们的返回值是一个对象时

          let getObj = id => {
              return {
                  id: id,
                  name: '刘德华'
              }
          }
          console.log(getObj(2))      // {id: 2, name: '刘德华'}

它也是只有一个返回值,但我们不能直接将对象写在箭头后面,需要用一个小括号将其包裹起来

          let getObj = id => ({
              id: id,
              name: '刘德华'
          })
          console.log(getObj(2))      // {id: 2, name: '刘德华'}

对象中的函数和箭头函数

在一个page对象中,有参数和方法

  let page = {
              id: 123,
              init: function () {
                  document.addEventListener('click', function (event)  {
                      console.log(this)   //这里的this是document
                      this.text(event.type)   //document中没有text方法,就会报错
                  })
              },
              text: function (type) {
                  console.log(`事件类型:${type},当前id:${this.id}`)
              }
          }
          page.init()  //报错

函数内部的this只能通过作用域链来查找确定,一旦使用箭头函数,当前就不存在作用域链了,就会向上一层寻找作用域链

  let page = {
              id: 123,
              init: function () {
                  document.addEventListener('click', (event) => {
                      console.log(this)   //这里的this指向对象page
                      this.text(event.type)   //page中可以查找到text方法
                  })
              },
              text: function (type) {
                  console.log(`事件类型:${type},当前id:${this.id}`)
                  // 打印输出:事件类型:click,当前id:123
              }
          }
          page.init()    //事件类型:click,当前id:123

若我们将init方法也改写为箭头函数时

          let page = {
              id: 123,
              init: () => {
                  document.addEventListener('click', (event) => {
                      console.log(this)   // this会在向上一层寻找,找到window
                      this.text(event.type)   //但window并没有text方法
                  })
              },
              text: function (type) {
                  console.log(`事件类型:${type},当前id:${this.id}`)
              }
          }
          page.init()     //报错:this.text is not a function (window中的text方法未定义)

解构赋值

完全解构

在ES5中我们获取一个对象中的值

          let obj = {
              uname: '张三',
              age: 18
          }
          let uname = obj.uname
          let age = obj.age
          console.log(uname, age)     // 张三 18

在ES6中我们使用解构赋值

          let { uname, age } = obj
          console.log(uname, age)     // 张三 18

不完全解构

          let obj = {
              a: {
                  name: 'andy',
                  age: 20
              },
              b: [2, 4, 6, 7, 9],
              c: 'hello,world'
          }
          let { a } = obj
          console.log(a)      // {name: 'andy', age: 20}
          //只获取a这一个属性值

我们还可以使用剩余运算符

          let { b, ...rest } = obj
          console.log(rest)     // {a: {…}, c: 'hello,world'}
          //这样a和c的值就保存在rest中了

重命名

          let { a: t } = obj
          console.log(t)      // {name: 'andy', age: 20}
          // 此时若再打印输出a则显示a is not defined

对数组解构

          let arr = [1, 6, 9]
          let [a, b, c] = arr
          console.log(a, b, c)    // 1, 6, 9

对象的扩展

在ES5中我们这样给一个对象写入值

          let uname = 'andy', age = 18
          let person = {
              uname: uname,
              age: age,
              getName: function () {
                  console.log(this.uname);
              }
          }
          console.log(person)         // {uname: 'andy', age: 18, getName: ƒ}
          person.getName()            // andy

ES6中简化写法

          let person = {
              uname,
              age,                // 直接将外部的属性及值拿到对象中来
              getName() {
                  console.log(this.uname)
              }                   // 不需要再写function
          }

assign()方法

          let target = {
              a: 1
          }
          let obj1 = {
              b: 2,
              c: 3
          }
          let obj2 = {
              d: 5
          }
          Object.assign(target, obj1, obj2)   //把第二个参数及后面的对象都合并给第一个
          console.log(target)     //{a: 1, b: 2, c: 3, d: 5}
  //浅拷贝语法糖
  Object.assign(other, obj)   //把obj拷贝给other 代替for in 

Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它属于 JavaScript 语言的原生数据类型之一,其他数据类型是:undefinednull、布尔值(Boolean)、字符串(String)、数值(Number)、大整数(BigInt)、对象(Object)。

Symbol 值通过Symbol()函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

          let uname1 = name
          let uname2 = name
          console.log(typeof (uname1))        // string
          console.log(uname1 === uname2)      // true
  ​
          let uname3 = Symbol(name)
          let uname4 = Symbol(name)
          console.log(typeof (uname3))        // symbol 原始数据类型
          console.log(uname3 === uname4)      // true

通过Symbol声明的属性名

          let name = Symbol('name')
          console.log(name)       //  Symbol(name)
          let obj = {
              [name]: 'andy'
          }
          console.log(obj)        //{Symbol(name): 'andy'}
          //在这个对象中的Symbol(name)这个属性名是独一无二的

当我们想获取对象中的Symbol属性值时

          console.log(obj[name])      //  andy
          console.log(obj.name)       //  undefined ----> 错误方法

属性名的遍历

          let name = Symbol('name')
          let obj = {
              [name]: 'andy'
          }
          let obj1 = {
              name: 'tom'
          }
          for (let k in obj) {
              console.log(k)
          }                   //在这个obj中的[name]属性属于symbol类型,无法使用for...in遍历得到属性名
          for (let k in obj1) {
              console.log(k)      //  name
          }                   //但这个obj1中的name属性是string型,for...in遍历可以得到属性名name

Symbol 值作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

  //在ES6中我们提供了Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
          let s = Object.getOwnPropertySymbols(obj)
          console.log(s)      //  [Symbol(name)]
  //另外,Reflect.ownKeys()方法也可以返回一个数组,包括所有类型的键名,包括常规键名和 Symbol 键名。
          let m = Reflect.ownKeys(obj)
          console.log(m)      //[Symbol(name)]        与Object.getOwnPropertySymbols()方法作用一样

Set和Map数据结构

Set

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。set本身是一个构造函数,用来生成 Set 数据结构。

          // 表示一个无重复值的集合:
          let data = new Set()
          console.log(data)
          // 添加元素
          data.add(22)    //添加一个数字
          data.add('31')  //添加一个字符串
          data.add('31')  //添加一个重复的字符串---重复的不会再添加显示到集合中
          data.add([1, 3, 6])     //添加一个数组
          console.log(data)       // Set(3) {22, '31', Array(3)}
          //删除元素
          data.delete(22)
          console.log(data)   // Set(2) {'31', Array(3)}
          console.log(data.size)  //  2 ,集合长度/元素个数
          console.log(data.has('31'))     //true,校验某个值是否在集合中

在set集合中,键值对是相等的,键就是值,值也是键

          console.log(data)       //  Set(2) {'31', Array(3)}
          data.forEach(
              (val, key) => {
                  console.log(val)
                  console.log(key)
              }
          )
  //打印输出:
      31
      31
      [1, 3, 6]
      [1, 3, 6]

set转换为数组,数组去重

          let set = new Set([1, 2, 3, 3, 3, 4])
          console.log(set)        //  Set(4) {1, 2, 3, 4} , 去除重复的
          let arr = [...set]      //  利用扩展运算符
          console.log(arr)        //  (4) [1, 2, 3, 4]
          //  以上方法就实现的数组去重功能

set实现字符串去重

          let set1 = new Set('aaabcdde')
          console.log(set1)       //Set(5) {'a', 'b', 'c', 'd', 'e'}
          str = [...set1]         // 利用扩展运算符转换为数组
          console.log(str)        //(5) ['a', 'b', 'c', 'd', 'e']
          str = [...set1].join('')    //  利用join方法拼接成字符串
          console.log(str)        //abcde
         //此时我们就实现了字符串的去重

Map

Map类型是键值对的集合,键和值可以是任意类型

      //添加元素 .set(key, value)方法,在Map中没有add方法
          let map = new Map()
          map.set('name', '张三')
          map.set(undefined, 12)
          map.set(666, 'm')
          map.set('name', 'tom')
          console.log(map)            //  Map(3) {'name' => 'tom', undefined => 12, 666 => 'm'}
  //set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。键可以是任意类型
      //获取元素  get方法
          console.log(map.get('name'))    // tom
      //校验是否含有某个属性    has()方法
          console.log(map.has('name'))    //true
      //删除元素  delete方法
          map.delete(666)
          console.log(map)        // Map(2) {'name' => 'tom', undefined => 12}
      //得到集合中元素的个数 size方法
          console.log(map.size)   //  2
      //清空 clear
          map.clear()
          console.log(map)    //  Map(0) {size: 0}

数组的扩展方法

from()将伪数组转换为真数组

          function add() {
              console.log(arguments)      //Arguments(4) [1, 3, 5, 9, callee: ƒ, Symbol(Symbol.iterator): ƒ]
              //接收到的参数是一个伪数组
              //  ES5方法
              let arr1 = [].slice.call(arguments)
              console.log(arr1)           //(4) [1, 3, 5, 9]
              //  ES6方法
              let arr2 = Array.from(arguments)
              console.log(arr2)           //(4) [1, 3, 5, 9]
          }
          add(1, 3, 5, 9)

当我们获取一个ul中的li集合时,我们希望将它转换为一个数组

      <ul>
          <li>1</li>
          <li>3</li>
          <li>6</li>
          <li>9</li>
      </ul>
      <script>
          let lis = document.querySelectorAll('li')
          console.log(lis)    //  NodeList(4) [li, li, li, li]
          //这里得到的是一个NodeList集合
      </script>

我们可以使用from方法将他转换为数组

          console.log(Array.from(lis))    //  (4) [li, li, li, li]

当然,我们还可以使用前面学习到的扩展运算符来转换

          console.log([...lis])   //  (4) [li, li, li, li]

当我们想获取li中的元素时,from可以有第二个参数

          console.log(Array.from(lis, lis => lis.innerHTML))  //  (4) ['1', '3', '6', '9']

of方法

          //of方法可以将一组任意类型的数据转换为一个数组
          console.log(Array.of(2, 6, 'andy', [11, 22], { id: 18 }))   //  (5) [2, 6, 'andy', Array(2), {…}]

copyWithin()方法

数组实例的copyWithin()方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。

  Array.prototype.copyWithin(target, start = 0, end = this.length)
  /*
  它接受三个参数。
      target(必需):从该位置开始替换数据。如果为负值,表示倒数。
      start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
      end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
  */
          console.log([1, 2, 3, 4, 5].copyWithin(0, 3))       //  (5) [4, 5, 3, 4, 5]
      //表示从索引号为0(1)的位置开始替换为索引号为3及其以后的数据(4,5)
          console.log([1, 2, 3, 4, 5].copyWithin(1, 3))       //  (5) [1, 4, 5, 4, 5]
      //表示从索引号为1(2)的位置开始替换为索引号为3及其以后的数据(4,5)  
          console.log([1, 2, 3, 4, 5].copyWithin(0, 3, 4))    //  (5) [4, 2, 3, 4, 5]
      //表示从索引号为0(1)的位置开始替换为索引号为3及其以后的数据但是只读取到索引号为4(5)的位置

find()和findIndex()

          console.log([2, 5, -10, 6, -7, -5, 3].find(n => n < 0))             //  10
          //  find 是找出数组中第一个符合条件的成员
          console.log([2, 5, -10, 6, -7, -5, 3].findIndex(n => n < 0))        //  2
          //  findIndex 是找出数组中第一个符合条件的成员的索引号

keys()和values()和entries()

ES6 提供三个新的方法——entries()keys()values()——用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

          let arr = ['a', 'b', 7, 3]
          console.log(arr)
      /*(4) ['a', 'b', 7, 3]      //这是一个数组
          0: "a"              
          1: "b"
          2: 7
          3: 3            //键值对形式,前面的一列数字就是键名,后面一列是键值
          length: 4
          [[Prototype]]: Array(0)
      */
  ​
          for (let index of arr.keys()) {
              console.log(index)
          }   //  keys() 对键名的遍历
      /*  
          1
          2
          3
          4
      */
  ​
          for (let ele of arr.values()) {
              console.log(ele)
          }   //  values() 对键值的遍历
      /*
          a
          b
          7
          3
      */
  ​
          for (let [index, ele] of arr.entries()) {
              console.log(index, ele)
          }   //  entries() 对键值对的遍历
      /*
          0 'a'
          1 'b'
          2 7
          3 3
      */

include()

include()方法返回一个布尔值,表示某个数组是否包含给定的值,判断某个元素是否在数组中

  //ES5中我们使用indexOf()方法返回首个被找到的元素在数组中的索引位置; 若没有找到则返回 -1
          console.log([1, 2, 3,].indexOf(3))      //  2
          console.log([1, 2, 3,].indexOf(7))      //  -1
  //则有
          console.log([1, 2, 3,].indexOf(3) === 2)    // true  ,  以此来表示在元素中有该值存在

ES6给我们提供了include()方法

          console.log([1, 2, 3].includes(3))      //  true
          console.log([1, 2, 3].includes('3'))    //  false

迭代器Iterator

迭代器是一个接口,能快捷的访问数据,通过Symbol.iterator来创建迭代器,通过迭代器的next()方法获得迭代之后的结果。其实遍历器就是一个用于遍历数据结构的指针

          let arr = ['one', 'two', 'three']
          //创建迭代器
          let iterator = arr[Symbol.iterator]()   
          console.log(iterator)                   // Array Iterator {}
          console.log(iterator.next())            // {value: 'one', done: false}
          console.log(iterator.next())            // {value: 'two', done: false}
          console.log(iterator.next())            // {value: 'three', done: false}
          console.log(iterator.next())            // {value: undefined, done: true}
  /*
  Iterator 的遍历过程是这样的:
  ​
  (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  ​
  (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  ​
  (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  ​
  (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
  ​
  每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束(false:未结束,true:结束)。
  */

生成器Generator

Generator函数可以通过yield关键字将函数挂起(让函数的执行暂停在某一阶段),为改变执行流提供了可能,也为异步编程提供了方案

Generator函数的function后面紧跟一个*,在函数内部使用yield表达式将函数挂起

          function* func() {
              console.log(111)
              yield 'hello'
              yield 'world'
              console.log(222)
          }
          func()  
  //Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,即遍历器对象,让我们在下面打印看看
          console.log(func())     //  func {<suspended>}  --->    遍历器对象
          let fn = func()         //这里我们将遍历器对象赋给fn
  //下一步,我们必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行
          fn.next()   //  111
  //当我们第一次调用next方法时,Generator 函数开始执行,遇到yield表达式就暂停后面的操作(停在yield 'hello'),并把紧跟yield后面的值作为返回对象value的值
          fn.next()
  //这是我们第二次调用next方法,Generator 函数接着上次停止的地方执行(从yield 'hello'往后执行),在这之间没有任何操作,所有我们会执行到下一个yield表达式(yield 'world'),停在这个地方不再继续执行,并返回对象value的值
          fn.next()   //  222
  //第三次调用next方法,Generator 函数继续执行(执行yield 'world'后面的语句),因此可以打印输出222,此时函数执行完毕,后面也没有yield表达式,所以返回的value值是undefined,done的属性变为true,函数执行完毕
  ​
  //让我们我们打印验证一下
          console.log(fn.next())      //  {value: 'hello', done: false}
          console.log(fn.next())      //  {value: 'world', done: false}
          console.log(fn.next())      //  {value: undefined, done: true}

Generator 函数从会上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。

Generator 与 Iterator 接口的关系

任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。

   //我们定义这样一个对象,我们可以看到在对象在是没有Symbol.iterator方法的,并且用扩展运算符遍历对象会报错
          let myIterable = {
              name: 'andy',
              age: 18
          }
          console.log(myIterable)
          /*
          {name: 'andy', age: 18}
              age: 18
              name: "andy"
              [[Prototype]]: Object
          */
          console.log([...myIterable])        //  TypeError: myIterable is not iterable

由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口

  //我们给对象添加一个Symbol.iterator属性,并且把Generator 函数赋值给Symbol.iterator属性,此时对象就具有 Iterator 接口
          let myIterable = {
              name: 'andy',
              age: 18
          }
          console.log(myIterable)
          /*
          {name: 'andy', age: 18}
              age: 18
              name: "andy"
              Symbol(Symbol.iterator): ƒ* ()
              [[Prototype]]: Object       
          */
          myIterable[Symbol.iterator] = function* () {
              yield 'andy';
              yield 18;
          };
          console.log([...myIterable])    //  ['andy', 18]    这样我们就可以用扩展运算符遍历这个对象

next方法的参数

          function* add() {
              console.log('start')    //  start
              let x = yield '2'
              console.log('one:' + x)     //  one:20
              let y = yield x
              console.log('total:' + y)   //  total:30
              return x + y
          }
          let fn = add()
  //在未使用next方法之前函数不会执行
          console.log(fn.next())      //  {value: '2', done: false}
  //第一次调用next方法,函数开始执行,打印输出start,然后遇到yield,函数暂停执行,并返回yield后面的表达式作为value的值
          console.log(fn.next(20))    //  {value: 20, done: false}
  //第二次调用next方法函数从上次暂停的地方开始执行,因为next携带了参数(20),它就会将上一次也就是让函数暂停的yield表达式的值设为这个参数(20),我们用x接收了这个表达式的值,所有x就是20,我们接着来执行函数打印 'one:' + x 会得到 one:20 ,然后我们的函数就执行到 let y = yield x ,在这里我们就又遇到yield了,函数暂停执行,返回yield后表达式的值给value,这里yield后面跟的是 x 因此它返回的值就是 20 ,所以我们 console.log(fn.next(20)) 会得到 {value: 20, done: false}
          console.log(fn.next(30))    //  {value: 50, done: true}
  //第三次调用接着执行,并再次把携带的参数(30)赋给上次的yield表达式,因此 y = 30,函数继续执行,一直执行到return语句,next返回的对象的value的属性值就是紧跟在return后面表达式的值,done的值变为true,遍历结束

for...of 循环

for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。

          function* func() {
              yield 1;
              yield 2;
              yield 3;
              yield 4;
              return 5;
          }
          for (let v of func()) {
              console.log(v)
          }   //  1 2 3 4
  //上面代码使用for...of循环,依次显示 4个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的5,不包括在for...of循环之中。

原生的 JavaScript 对象没有遍历接口,无法使用for...of循环,通过 Generator 函数为它加上这个接口,就可以用了。

          let obj = {
              name: '小马哥',
              age: 18
          }
          //给对象添加一个Symbol.iterator属性,并且把Generator 函数赋值给Symbol.iterator属性,使对象具有 Iterator 接口
          obj[Symbol.iterator] = objectEntries
          function* objectEntries(obj) {
              //获取对象所有的key以数组形式保存起来,['name', 'age']
              let propKeys = Object.keys(obj)     //  propKeys中是['name', 'age']
              for (let propkey of propKeys) {     //  利用for...of把键(name和age)从propKeys中遍历出来,propKey分别是name和age
                  yield [propkey, obj[propkey]]   //  把obj中的值对应给到相应的键,obj[name]就是obj中name属性的值,把它组成一个数组[name,obj[name]],即[name,小马哥],遍历完成之后就是[name,小马哥]  [age,18]
              }
          }
  //在上面的操作中我们一个给这个原生对象通过 Generator 函数objectEntries为它加上遍历器接口,就可以用for...of遍历了。除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口
          for (let [key, value] of objectEntries(obj)) {
              // console.log([key, value])    
              /*
                  ['name', '小马哥']
                  ['age', 18]
              */
              // console.log(key)
              /*
                  name
                  age
              */            
              // console.log(value)
              /*
                  小马哥
                  18
              */   
              console.log(`${key}: ${value}`)
              /*
                  name: 小马哥
                  age: 18
              */ 
              //在for...of中我们可以以任何想要的形式遍历出对象中的元素
          }
  //扩展运算符
          console.log([...objectEntries(obj)])
          /*
          (2) [Array(2), Array(2)]
              0: (2) ['name', '小马哥']
              1: (2) ['age', 18]
                  length: 2
              [[Prototype]]: Array(0)
          */
  //Array.from 方法
          console.log(Array.from(objectEntries(obj)))
          /*
          (2) [Array(2), Array(2)]
              0: (2) ['name', '小马哥']
              1: (2) ['age', 18]
                  length: 2
              [[Prototype]]: Array(0)
          */
  // 解构赋值
          let [x, y] = objectEntries(obj)
          console.log(x)      //  ['name', '小马哥']
          console.log(y)      //  ['age', 18]

应用-异步操作同步化

当我们在初次进入某个页面时,会首先显示等待页面,然后数据加载完成,再隐藏这个等待页面

          function loadUI() {
              console.log('加载loading……页面')
          }
          function getData() {
              setTimeout(() => {
                  console.log('数据加载完成')
              }, 1000);
          }
          // 在这个获取数据函数中,我们模拟获取数据需要一定的时间,模拟一个异步操作
          function hideUI() {
              console.log('隐藏loading……页面')
          }
          loadUI()
          getData()
          hideUI()
  //我们想要的效果是在数据加载完成后隐藏等待页面,但由于loadUI和hideUI是同步任务,getData是异步任务,因此如果我们直接执行的话,就会导致同步任务执行完再执行异步任务,也就是说当我们看到loading页面但是数据还没加载完毕,但是这个loading页面以及被隐藏了
  /*执行结果就是:
      加载loading……页面
      隐藏loading……页面
      数据加载完成          ----> 在前面两个同步任务完成后才会执行这个
  */

因此我们就需要创建一个生成器,使用Generator函数让异步操作同步化

          function loadUI() {
              console.log('加载loading……页面')
          }
          function getData() {
              setTimeout(() => {
                  console.log('数据加载完成')
                  Iterator.next()
              }, 1000);
          }
          function hideUI() {
              console.log('隐藏loading……页面')
          }
          function* load() {
              loadUI()
              yield getData()
              hideUI()
          }
      //当我们在一个生成器函数中调用这些函数时,他们并不会直接执行
          let Iterator = load()
      //调用生成器函数,函数不会执行,会返回一个遍历器对象
          Iterator.next()
      //当我们第一次调用next时,生成器函数开始从头执行,loadUI()函数执行,然后遇到yield挂载函数,函数会停到getData(),函数不再继续执行,但是getData()会正常执行只是不会再往下一步执行,巧妙地是我们在getData()函数中的最后一步再次调用next,此时hideUI()函数开始调用执行,至此就实现了我们想要的效果

补充一点,当我们函数执行时遇到关键字yield时他会挂载到当前yield表达式,如果这个表达式是一个值,他会返回到对象中作为value的值,如果这个表达式是一个操作他返回的对象的value值是undefined,但这个表达式会执行,等待下一次next调用再往后执行

          function* func() {
              console.log(111)
              yield console.log('hello')
              yield 6
              console.log(222)
          }
          let fn = func()
          fn.next()   //  111  hello
          //第一次调用函数从头执行,输出两条语句,意味着函数挂载在yield console.log('hello')这条语句时,yield后面的操作是被执行了的
          fn.next()   
          //第二次调用,函数继续执行,然后挂载到yield 6,因此没有任何输出
          fn.next()   //  222
          //第三次调用,函数执行yield 6后面的语句,即console.log(222),因此打印出222

让我们打印看看每次调用next他会给对象中的value返回什么值

          console.log(fn.next())  //{value: undefined, done: false}
  //第一次调用他会挂载在yield console.log('hello')并返回yield后的值,此处yield后面是一个操作表达式,因此返回的是undefined
          console.log(fn.next())  //{value: 6, done: false}
  //第二次调用挂载在yield 6,返回对象中value的值为6
          console.log(fn.next())  //{value: undefined, done: true}
  //第三次调用函数直接执行完,没有返回的value值,done的属性变为true表示函数执行完毕

Promise对象

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。

Promise对象的基本使用

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

          let pro = new Promise(function (resolve, reject) {
              //模拟从服务器获取数据,执行异步操作
              /* //获取成功返回的数据:
              let res = {
                  code: 200,
                  data: {
                      name: '小马哥',
                      age: 18
                  }
              } */
              //获取失败返回的数据:
              let res = {
                  code: 201,
                  error: '获取数据失败'
              }
              setTimeout(() => {
                  if (res.code === 200) {
                      resolve(res.data)
                  } else {
                      reject(res.error)
                  }
              }, 1000);
          })
          pro.then((value) => {
              console.log(value); //这里的value就是获取数据成功resolve返回的res.data
          }, (error) => {
              console.log(error)  //这里的error就是获取数据失败reject返回的res.error
          })
  //Promise封装一个定时器函数
          function timeout(ms) {
              return new Promise((resolve, reject) => {
                  setTimeout(() => {
                      resolve('promise success')
                  }, ms);
              })
          }
          console.log(timeout())  //Promise {<pending>}   调用timeout(ms)其实就是把new的promise对象返回出来
          timeout(2000).then((val) => {
              console.log(val)
          })

then()方法

采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用

  //当我们打印上述pro.then()
          console.log(pro.then((value) => {
              console.log(value);
          }, (error) => {
              console.log(error)
          }))     //  Promise {<pending>}
  //我们发现打印输出的仍然是一个promise对象,也就意味着它可以继续调用then方法,这就是链式编程

 catch()方法

catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数

          pro.then((value) => {
              console.log(value); 
          }, (error) => {
              console.log(error)  
          })
  //上面的pro.then()也可以改写为这样:
          pro.then((value) => {
              console.log(value);
          }).then(null, (error) => {
              console.log(error)
          })
  //它也等价于用catch()方法捕获运行中抛出的错误
          pro.then((value) => {
              console.log(value);
          }).catch((error) => {
              console.log(error)
          })
  //这种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数

all()方法

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

          let Promise1 = new Promise((resolve, reject) => { })
          let Promise2 = new Promise((resolve, reject) => { })
          let Promise3 = new Promise((resolve, reject) => { })
          let Promise4 = Promise.all([Promise1, Promise2, Promise3])
          Promise4.then((data) => {
              console.log(data)   //  三个都成功promise4才算成功,才能走到这里
          }).catch((error) => {
              console.log(error)  //  否则有一个失败就整个失败了,往这走
          })

race()方法

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

  let p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

          function requestImg(imgSrc) {
              return new Promise((resolve, reject) => {
                  let img = new Image()
                  img.onload = function () {
                      resolve(img)
                  }
                  img.src = imgSrc
              })
          }
          function timeout() {
              return new Promise((resolve, reject) => {
                  setTimeout(() => {
                      reject(new Error('图片请求超时'))
                  }, 3000);
              })
          }
          let url = 'https://img2.baidu.com/it/u=1395980100,2999837177&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=675'
          Promise.race([requestImg(url), timeout()]).then((data) => {
              document.body.appendChild(data)
          }).catch((err) => {
              console.log(err)
          })
  //在上述函数中requestImg和timeout两个函数分别返回两个Promise实例,在Promise.race([requestImg(url), timeout()])中这两个函数谁先执行完,也就是说谁的状态先改变就把谁的返回值传递给Promise.race([requestImg(url), timeout()])的回调函数,若requestImg先完成则返回的是resolve那么Promise.race([requestImg(url), timeout()])就会执行.then((data) => {document.body.appendChild(data)}),若一直请求不到图片,三秒钟之后timeout就会执行,返回reject,那么函数就会通过catch捕捉到他返回的错误(简单来说就是竞争执行,谁先执行完相应调用那个回调函数,成功是resolve就会调用.then。失败则是reject调用.catch)

async函数

看文档去吧,,,没听懂

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值