ES6/ECMAScript2015 介绍

不一样的Javascript 进阶
@蚊子

基础

  • 块级作用域
  • 变量的解构赋值
  • 字符串的扩展
  • 正则的扩展
  • Math对象的扩展
  • Number对象的扩展
  • 数组的扩展
  • 扩展运算符
  • 函数绑定
  • 箭头函数
  • for...of 循环
  • Set & Map
  • Iterator迭代器

回调地狱 callback hell

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

如何解决回调地狱问题

  • 初级解决方案:Generator & yield : co
  • 高级解决方案:Promise : bluebird, Q, ES6.promise
  • 终极解决方案:Async/await : Promise + Generator 的语法糖

初级解决方案 - Generator函数 & yield

yield语句

  • 在Generator函数内使用,作用是暂停当前执行的线程,启用一个用户空间线程(协程)去执行yield语句后面的代码。
  • yield语句后面的表达式,只有当调用next方法时才会执行。(co库就是解决这问题)
  • Generator函数可以不用yield语句,这时就变成了一个单纯的暂缓执行函数。
    function* f() {
      console.log('执行了!')
    }
    var generator = f();
    setTimeout(function () {
      generator.next()
    }, 2000);
    

yield* 语句

  • 在 Generator 函数内部调用另一个 Generator 函数是没有效果的。

    function* foo() {
    
    }
    function* bar() {
      yield 'x';
      foo();
      yield 'y';
    }
    
  • yield* 语句可以,可实现递归调用
    function* bar() {
      yield 'x';
      yield* foo();
      yield 'y';
    }
    

Generator函数原型与this

  • Generator函数也有prototype属性,同样可以继承原型方法。

    function* g() {
    
    }
    g.prototype.hello = function () {
      return 'hi!';
    };
    let obj = g();
    obj instanceof g // true
    obj.hello() // hi!
    
  • Generator函数总是返回一个遍历器对象,而不是this对象
    function* g() {
      this.a = 11;
    }
    let obj = g();
    obj.a // undefined
    
  • Generator函数不能跟new命令一起用,会报错。

应用 - 异步操作的同步化表达

  • 利用Generator函数和yield语句可以代替异步回调

    function* loadUI() {
      showLoadingScreen();
      yield loadUIDataAsynchronously();
      hideLoadingScreen();
    }
    var loader = loadUI();
    
    loader.next();  // 加载UI
    loader.next();  // 卸载UI
    
  • 用Generator函数逐行读取文本文件
    function* numbers() {
      let file = new FileReader("numbers.txt");
      try {
        while(!file.eof) {
          yield parseInt(file.readLine(), 10);
        }
      } finally {
        file.close();
      }
    }
    

应用 - 控制流管理

  • 如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样
    step1(function (value1) {
      step2(value1, function(value2) {
        step3(value2, function(value3) {
          step4(value3, function(value4) {
            // Do something with value4
          });
        });
      });
    });
    
  • Generator函数可以进一步改善代码运行流程
    function* longRunningTask(value1) {
      try {
        var value2 = yield step1(value1);
        var value3 = yield step2(value2);
        var value4 = yield step3(value3);
        var value5 = yield step4(value4);
        // Do something with value4
      } catch (e) {
        // Handle any error from step1 through step4
      }
    }
    

高级解决方案 - Promise

Promise 如何解决回调问题

  • Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。

    var promise = new Promise(function(resolve, reject) {resolve('aaa')});
    promise.then(function(value) {
      // success
      console.log(value); // 输出 aaa, 如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数
    }, function(error) {
      // failure
    });
    
  • Promise实例的状态变为Resolved,就会触发then方法绑定的回调函数
  • then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行
  • Promise实现机制有多种规范,目前主流实现都遵循 Promise/A+规范,比如 bluebird, ES6.promise

Promise 解决回调问题实例

  • 异步加载图片

    function loadImageAsync(url) {
      return new Promise(function(resolve, reject) {
        var image = new Image();
        // 加载成功
        image.onload = function() {
          resolve(image);
        };
        // 加载失败
        image.onerror = function() {
          reject(new Error('Could not load image at ' + url));
        };
    
        image.src = url;
      });
    }
    

Promise.resolve

  • Promise.resolve等价于下面的写法
    Promise.resolve('foo')
    // 等价于
    new Promise(resolve => resolve('foo'))
    
  • 将现有对象转为Promise对象
    // 将jQuery生成的deferred对象,转为一个新的Promise对象
    var jsPromise = Promise.resolve($.ajax('/whatever.json'));
    

Promise.resolve 参数

  • 参数是一个Promise实例,直接返回该实例
  • 参数是一个thenable对象,thenable对象指的是具有then方法的对象
    let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };
    let p1 = Promise.resolve(thenable);
    p1.then(function(value) {
      console.log(value);  // 42
    });
    

Promise.resolve 参数

  • 参数不是具有then方法的对象,返回一个新的Promise对象,状态为Resolved
    var p = Promise.resolve('Hello');
    p.then(function (s){
      console.log(s)
    });
    // Hello
    
  • 不带有任何参数, 直接返回一个Resolved状态的Promise对象
    var p = Promise.resolve();
    p.then(function () {
      // ...
    });
    

Promise.reject

  • Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

    var p = Promise.reject('出错了');
    // 等同于
    var p = new Promise((resolve, reject) => reject('出错了'))
    
    p.then(null, function (s) {
      console.log(s)
    });
    

Promise.catch

  • 如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数
    var promise = new Promise(
      function(resolve, reject) {
        throw new Error('test');
        // 或者 reject(new Error('test'));
    });
    promise.catch(function(error) {
      console.log(error);
    });
    // Error: test
    
  • 建议总是使用catch方法,而不使用then方法的第二个参数reject来处理异常错误

Promise.all

  • Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
    // p1、p2、p3都是 Promise 实例
    var p = Promise.all([p1, p2, p3]);
    
  • p的状态由p1、p2、p3决定,分成两种情况
    1. 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled
    2. 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected
    
  • 例子

    // 生成一个Promise对象的数组
    var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
      return getJSON('/post/' + id + ".json");
    });
    
    Promise.all(promises).then(function (posts) {
      // ...
    }).catch(function(reason){
      // ...
    });
    

Promise.race

  • Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例
    // p1、p2、p3都是 Promise 实例
    var p = Promise.all([p1, p2, p3]);
    
  • 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数

Promise 应用场景

  • 异步请求,使用promise可以使执行流程清晰明了
  • 需要管道,管道指当前任务的输出可以作为下一个任务的输入,形成一条数据管道,then方法

终极解决方案 - async/await

例子:读取文件

  • Generator 函数版本

    var fs = require('fs');
    var readFile = function (fileName) {
      return new Promise(function (resolve, reject) {
        fs.readFile(fileName, function(error, data) {
          if (error) reject(error);
          resolve(data);
        });
      });
    };
    
    var gen = function* () {
      var f1 = yield readFile('/etc/fstab');
      var f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    
  • Async函数版本
    var fs = require('fs');
    var readFile = function (fileName) {
      return new Promise(function (resolve, reject) {
        fs.readFile(fileName, function(error, data) {
          if (error) reject(error);
          resolve(data);
        });
      });
    };
    var asyncReadFile = async function () {
      var f1 = await readFile('/etc/fstab');
      var f2 = await readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    

Async函数对 Generator 函数的改进,体现在以下四点

  • 内置执行器,Generator 函数需要借助co等库才能做到
  • 更好的语义,async和await,比起星号和yield,语义更清楚
  • 更广的适用性,async函数的await命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
  • 返回值是 Promise,比 Generator 函数的返回值是 Iterator 对象方便多了

async 函数使用形式

// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
  async getAvatar(name) {
    const cache = await cachePromise(name);
    return cache;
  }
}
const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};

Async函数的 Promise

  • async函数返回一个 Promise 对象,async函数内部return语句返回的值,会成为then方法回调函数的参数

    async function f() {
      return 'hello world';
    }
    
    f().then(v => console.log(v))
    // "hello world"
    
  • async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态

    async function f() {
      throw new Error('出错了');
    }
    
    f().then(
      v => console.log(v),
      e => console.log(e)
    )
    

Async函数的 Promise

  • 只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数
    async function getTitle(url) {
      let response = await fetch(url);  // 抓取网页
      let html = await response.text(); // 取出文本
      return html.match(/<title>([\s\S]+)<\/title>/i)[1];
    }
    getTitle('https://tc39.github.io/ecma262/').then(console.log)
    // "ECMAScript 2017 Language Specification"
    
  • await命令后面是一个 Promise 对象,如果不是,会被转成一个立即resolve的 Promise 对象

    async function f() {
      return await 123;
    }
    
    f().then(v => console.log(v))
    // 123
    

Async函数的 Promise

  • 只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
    async function f() {
      await Promise.reject('出错了');
      await Promise.resolve('hello world'); // 不会执行
    }
    
  • 上面问题可以用try...catch来解决
    async function f() {
      try {
        await Promise.reject('出错了');
      } catch(e) {
      }
      return await Promise.resolve('hello world'); // 会执行
    }
    f().then(v => console.log(v))
    // hello world
    
  • 最佳实践:尽量将await异步操作放在try...catch代码块之中,并在catch里处理异常

async 函数的实现原理

  • async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

    async function fn(args) {
      // ...
    }
    
    // 等同于
    function fn(args) {
      return spawn(function* () {  // spawn函数就是自动执行器
        // ...
      });
    }
    

spawn函数

  • spawn函数,自动执行器代码
    function spawn(genF) {
      return new Promise(function(resolve, reject) {
        var gen = genF();
        function step(nextF) {
          try {
            var next = nextF();
          } catch(e) {
            return reject(e);
          }
          if(next.done) {
            return resolve(next.value);
          }
          Promise.resolve(next.value).then(function(v) {
            step(function() { return gen.next(v); }); // 递归自动执行next
          }, function(e) {
            step(function() { return gen.throw(e); });
          });
        }
        step(function() { return gen.next(undefined); });
      });
    }
    

Class 类 - 面向对象

class类与prototype原型

  • 函数的prototype属性,在 ES6 的“类”上面继续存在
    class Point {
      constructor() {}
      toString() {}
      toValue() {}
    }
    // 等同于
    Point.prototype = {
      constructor() {},
      toString() {},
      toValue() {},
    };
    
  • 通过Object.assign给类添加方法
    class Point {
      constructor(){}
    }
    Object.assign(Point.prototype, {
      toString(){},
      toValue(){}
    });
    Point.prototype.constructor === Point // true
    

模块化

Symbol - 第7种数据类型

undefined

Proxy - 函数劫持

undefined

Reflect

undefined

理解ES6的评判标准是什么?

  • 理解Promise, Generator, async/await, 并能看懂别人的代码
  • 理解变量的解构赋值,并能看懂别人的代码
  • 理解箭头函数,并能看懂别人的代码
  • 理解面向对象编程Class类,并能看懂别人的代码
  • 理解iterator迭代器接口
  • 用模块化方式开发项目

理解ES6的特性的好处

  • 还用说吗??
  • 学习开源框架或者参与开源项目,会更加轻松愉快
  • ES6是前端技术发展的里程碑,理解ES6可以更好地理解ES7, ES8...

谢谢

欢迎关注 github