ES6

163 阅读7分钟

ES6

1. let/var

块级作用域

  • JS中使用 var 来声明一个变量时,变量的作用域主要是和函数的定义有关
  • 针对于其它块定义来水是没有作用域的,比如 for/if

image.png

<body>

<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>

<script>
  // ES5中的var是没有块级作用域的(if/for)
  // ES6中的let是由块级作用的(if/for)

  // ES5之前因为if和for都没有块级作用域的概念, 所以在很多时候, 我们都必须借助于function的作用域来解决应用外面变量的问题.
  // ES6中,加入了let, let它是有if和for的块级作用域.
  // 1.变量作用域: 变量在什么范围内是可用.
  // {
  //   var name = 'why';
  //   console.log(name);
  // }
  // console.log(name);

  // 2.没有块级作用域引起的问题: if的块级
  // var func;
  // if (true) {
  //   var name = 'why';
  //   func = function () {
  //     console.log(name);
  //   }
  //   // func()
  // }
  // name = 'kobe'
  // func()
  // // console.log(name);

  var name = 'why'
  function abc(bbb) { // bbb = 'why'
    console.log(bbb);
  }
  // 将why传进去,无论外面怎么改,都不会有影响
  abc(name)
  name = 'kobe'

  // 3.没有块级作用域引起的问题: for的块级
  // 为什么闭包可以解决问题: 函数是一个作用域.ES5必须要借助闭包去解决块级作用域
  // var btns = document.getElementsByTagName('button');
  // for (var i=0; i<btns.length; i++) {
  //   (function (num) { // 0
  //     btns[i].addEventListener('click', function () {
  //       console.log('第' + num + '个按钮被点击');
  //     })
  //   })(i)
  // }

  const btns = document.getElementsByTagName('button')
  for (let i = 0; i < btns.length; i++) {
    btns[i].addEventListener('click', function () {
      console.log('第' + i + '个按钮被点击');
    })
  }
  
 

</script>

</body>

2. const

  • 使用const修饰的标识符为常量, 不可以再次赋值
    建议: 在ES6开发中,优先使用const, 只有需要改变某一个标识符的时候才使用let
<script>
  // 1.注意一: 一旦给const修饰的标识符被赋值之后, 不能修改
  // const name = 'why';
  // name = 'abc';

  // 2.注意二: 在使用const定义标识符,必须进行赋值
  // const name;

  // 3.注意三: 常量的含义是指向的对象不能修改, 但是可以改变对象内部的属性.
  const obj = {
    name: 'why',
    age: 18,
    height: 1.88
  }
  // 内存地址不能改变
  // obj = {}
  console.log(obj);

  obj.name = 'kobe';
  obj.age = 40;
  obj.height = 1.87;

  console.log(obj);
</script>

3. 对象字面量的增强写法

ES6中,对对象字面量进行了很多增强。

<script>
  // const obj = new Object()

  // const obj = {
  //   name: 'why',
  //   age: 18,
  //   run: function () {
  //     console.log('在奔跑');
  //   },
  //   eat: function () {
  //     console.log('在次东西');
  //   }
  // }

  // 1.属性的增强写法
  const name = 'why';
  const age = 18;
  const height = 1.88

  // ES5的写法
  // const obj = {
  //   name: name,
  //   age: age,
  //   height: height
  // }
  // ES6的写法
  const obj = {
     name,
     age,
     height,
   }
  console.log(obj);


  // 2.函数的增强写法
  // ES5的写法
  // const obj = {
  //   run: function () {
  //
  //   },
  //   eat: function () {
  //
  //   }
  // }
  // ES6的写法
  const obj = {
    run() {

    },
    eat() {

    }
  }
</script>

4. 箭头函数

基本使用

const ccc = () => { }

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<script>
  // 箭头函数: 也是一种定义函数的方式
  // 1.定义函数的方式: function
  const aaa = function () {

  }

  // 2.对象字面量中定义函数
  const obj = {
    bbb() {

    }
  }

  // 3.ES6中的箭头函数
  // const ccc = (参数列表) => {
  //
  // }
  const ccc = () => {

  }

</script>
</body>
</html>

箭头函数参数和返回值

  1. 一个参数时,省略小括号
  2. 函数代码块只有一行,省略大括号和return    如:render: h => h(App)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>


<script>
  // 1.参数问题:
  // 1.1.放入两个参数
  const sum = (num1, num2) => {
    return num1 + num2
  }

  // 1.2.放入一个参数,小括号可以省掉
  const power = num => {
    return num * num
  }

  // 2.函数中
  // 2.1.函数代码块中有多行代码时
  const test = () => {
    // 1.打印Hello World
    console.log('Hello World');

    // 2.打印Hello Vuejs
    console.log('Hello Vuejs');
  }

  // 2.2.函数代码块中只有一行代码
  // const mul = (num1, num2) => {
  //   return num1 + num2
  // }
  const mul = (num1, num2) => num1 * num2
  console.log(mul(20, 30)); // 600 不需要return执行的结果自动返回

  // const demo = () => {
  //   console.log('Hello Demo'); // Hello Demo
  // }
  const demo = () => console.log('Hello Demo')
  console.log(demo()); // undefined

  
  // render: (h) => {
  //   return h(App)
  // }
  // 只有一个参数小括号省略,函数代码块只有一行,省略大括号和return
  // render: h => h(App)
</script>
</body>
</html>

箭头函数中的this使用

  1. 一般将一个函数作为另一个函数的参数的时候,使用箭头函数
  2. 箭头函数的this查找:向外层作用域中, 一层层查找this, 直到有this的定义
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<script>
  // 什么时候使用箭头,将一个函数作为另一个函数的参数的时候
  setTimeout(function () {
    console.log(this); //  Window
  }, 1000)
  
  setTimeout(() => {
    console.log(this); // Window
  }, 1000)

  // 问题: 箭头函数中的this是如何查找的了?
  // 答案: 向外层作用域中, 一层层查找this, 直到有this的定义.
  const obj = {
    aaa() {
      setTimeout(function () {
        console.log(this); // window
      })
  
      setTimeout(() => {
        console.log(this); // obj对象
      })
    }
  }
  
  obj.aaa()


  const obj1 = {
    aaa() {
      setTimeout(function () {
        setTimeout(function () {
          console.log(this); // window
        })

        setTimeout(() => {
          console.log(this); // window
        })
      })

      setTimeout(() => {
        setTimeout(function () {
          console.log(this); // window
        })

        setTimeout(() => {
          console.log(this); // obj
        })
      })
    }
  }

  obj1.aaa()
</script>
</body>
</html>

5. Promise

什么是Promise

  • ES6中一个非常重要和好用的特性就是Promise
  • Promise到底是做什么的呢?
    • Promise是异步编程的一种解决方案。
  • 那什么时候我们会来处理异步事件呢?
    • 一种很常见的场景应该就是网络请求了。
    • 我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的3+4=7一样将结果返回。
    • 所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。
    • 如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。
  • 但是,当网络请求非常复杂时,就会出现回调地狱。

网络请求的回调地狱

  • 以下场景
    • 我们需要通过一个url1从服务器加载一个数据data1,data1中包含了下一个请求的url2

    • 我们需要通过data1取出url2,从服务器加载数据data2,data2中包含了下一个请求的url3

    • 我们需要通过data2取出url3,从服务器加载数据data3,data3中包含了下一个请求的url4

    • 发送网络请求url4,获取最终的数据data4

      image.png

      这样的代码难看而且不容易维护。
      我们更加期望的是一种更加优雅的方式来进行这种异步操作。
      Promise可以以一种非常优雅的方式来解决这个问题。

Promise的基本语法

定时器的异步事件

  • 这里,我们用一个定时器来模拟异步事件:

    • 假设下面的data是从网络上1秒后请求的数据
    • console.log就是我们的处理方式。
      // 1.使用setTimeout  是异步操作
        setTimeout(() => {
          console.log('Hello World');
        }, 1000)
      
  • 这是我们过去的处理方式,我们将它换成Promise代码

      ```js
      new Promise((resolve, reject) => {
          setTimeout(() => {
            // 成功的时候调用resolve 
            resolve('Hello World')
            // 失败的时候调用reject
            reject('error message')
          }, 1000)
        }).then((data) => {
          console.log(data); // Hello World
        }).catch((err) => {
          console.log(err); // error message
        })
      ```
    
  • 这个例子会让我们感觉多此一举

    • 首先,下面的Promise代码明显比上面的代码看起来还要复杂。
    • 其次,下面的Promise代码中包含的resolve、reject、then、catch都是些什么东西?
  • 我们先不管第一个复杂度的问题,因为这样的小程序根本看不出来Promise真正的作用。

定时器异步事件解析

  • new Promise很明显是创建一个Promise对象
  • 小括号中((resolve, reject) => {})也很明显就是一个函数,而且我们这里用的是之前刚刚学习过的箭头函数。
    1. 但是resolve, reject它们是什么呢?
    2. 我们先知道一个事实:在创建Promise时,传入的这个箭头函数是固定的(一般我们都会这样写)
    3. resolve和reject它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个。
  • 成功还是失败?
    1. 如果是成功的,那么通常我们会调用resolve(messsage),这个时候,我们后续的then会被回调。
    2. 如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调。
  • 这就是Promise最基本的使用了。

Promise三种状态

image.png

  • 当我们开发中有异步操作时, 就可以给异步操作包装一个Promise
    • 异步操作之后会有三种状态
  • 我们一起来看一下这三种状态:
    1. pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
    2. fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
    3. reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()

Promise写法

new Promise((resolve, reject) => {}).then().catch() image.png

new Promise((resolve, reject) => {}).then(参数一, 参数二) image.png

Promise链式调用

  • 我们在看Promise的流程图时,发现无论是then还是catch都可以返回一个Promise对象。
  • 所以,我们的代码其实是可以进行链式调用的:
  • 这里我们直接通过Promise包装了一下新的数据,将Promise对象返回了
    1. Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数
    2. Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数

链式调用简写

  1. return Promise(resolve => resolve(data))
  2. return Promise.resolve(data)
  3. return data
  • 简写一
    return Promise(resolve => resolve(结果))

image.png

// 一、new Promise(resolve => resolve(结果))

new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('aaa')
    }, 1000)
  }).then(res => {
    console.log(res, '第一层的10行处理代码');
  
    // 对结果进行第一次处理
    return new Promise((resolve, reject) => {
      resolve(res + '111')
      // reject('err')
    })
  }).then(res => {
    console.log(res, '第二层的10行处理代码');
  
    return new Promise(resolve => {
      resolve(res + '222')
    })
  }).then(res => {
    console.log(res, '第三层的10行处理代码');
  }).catch(err => {
    console.log(err);
  })
  • 简写二
    return Promise.resolve(结果)
// 二、new Promise.resolve(结果)
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('aaa')
    }, 1000)
  }).then(res => {
    console.log(res, '第一层的10行处理代码');

    // 对结果进行第一次处理
    return Promise.resolve(res + '111')
  }).then(res => {
    console.log(res, '第二层的10行处理代码');

    return Promise.resolve(res + '222')
  }).then(res => {
    console.log(res, '第三层的10行处理代码');
  })
  • 简写三
    return 结果
 // 三、省略掉Promise.resolve
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('aaa')
    }, 1000)
  }).then(res => {
    console.log(res, '第一层的10行处理代码');

    // 对结果进行第一次处理
    // 内部会对res + '111'进行Promise包装,并且内部会去调用resolve
    return res + '111'
  }).then(res => {
    console.log(res, '第二层的10行处理代码');

    return res + '222'
  }).then(res => {
    console.log(res, '第三层的10行处理代码');
  })

Promise抛出异常

// 四、如果某一层失败了,
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('aaa')
    }, 1000)
  }).then(res => {
    // 1.自己处理10行代码
    console.log(res, '第一层的10行处理代码');

    // 2.对结果进行第一次处理 (1)失败调用reject进入catch执行
    // return Promise.reject('error message')
    // (2)手动抛出异常throw
    throw 'error message'
  }).then(res => {
    console.log(res, '第二层的10行处理代码');

    return Promise.resolve(res + '222')
  }).then(res => {
    console.log(res, '第三层的10行处理代码');
  }).catch(err => {
    console.log(err);
  })

Promise的all方法

 // 五、需求:将两个请求返回的结果合并后再做其他操作
 // 没有Promise之前
  // 网络请求一:
  let isResult1 = false
  let isResult2 = false
  $ajax({
    url: '',
    success: function () {
      console.log('结果1');
      isResult1 = true
      handleResult()
    }
  })
  // 网络请求二:
  $ajax({
    url: '',
    success: function () {
      console.log('结果2');
      isResult2 = true
      handleResult()
    }
  })
  // 如何判断两个请求都拿到了再做后续操作,通过添加两个变量
  function handleResult() {
    if (isResult1 && isResult2) {
      //
    }
  }
  // 更优方案:
  // 使用Promise直接包装两个异步请求,在两个异步请求都完成的时候,在一个地方统一进行回调
   // 六、通过Promise的all方法解决多个异步请求都完成后获取结果
  // all 传入数组 
  // 数组里new两个Promise
  // 会在内部帮你判断这两个网络请求有没有都完成,如果都完成了,就会执行all([]).then() 进入这里的then
  // results为数组
  Promise.all([
      // new Promise((resolve, reject) => {
      //   $.ajax({
      //     url: 'url1',
      //     success: function (data) {
      //       resolve(data)
      //     }
      //   })
      // }),
      // new Promise((resolve, reject) => {
      //   $.ajax({
      //     url: 'url2',
      //     success: function (data) {
      //       resolve(data)
      //     }
      //   })
      // })

    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({name: 'why', age: 18})
      }, 2000)
    }),
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({name: 'kobe', age: 19})
      }, 1000)
    })
  ]).then(results => {
    console.log(results);
    // results[0] 为第一个请求执行的结果
    // results[1] 为第二个请求执行的结果
    // 最终是把两个异步请求的结果都放到了results数组里,这里的ajax请求通过setTimeout模拟异步请求
  })