从零开始学 Web 之 ES6(五)ES6基础语法三

你们好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新......前端

在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享一些好玩的项目。如今就让咱们一块儿进入 Web 前端学习的冒险之旅吧!git

1、Generator

如下来自 ECMAScript 6 入门 - 阮一峰es6

Generator 函数是 ES6 提供的一种异步编程解决方案。github

Generator 函数有多种理解角度。语法上,首先能够把它理解成,Generator 函数是一个状态机,封装了多个内部状态。ajax

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,仍是一个遍历器对象生成函数。返回的遍历器对象,能够依次遍历 Generator 函数内部的每个状态。编程

形式上,Generator 函数是一个普通函数,可是有两个特征。微信

一是,function关键字与函数名之间有一个星号;异步

二是,函数体内部使用yield表达式,定义不一样的内部状态(yield在英语里的意思就是“产出”)。async

function* myGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
// 返回值是一个遍历器对象
var hw = myGenerator();

上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(helloworld),即该函数有三个状态:hello,world 和 return 语句(结束执行)。异步编程

而后,Generator 函数的调用方法与普通函数同样,也是在函数名后面加上一对圆括号。不一样的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法能够恢复执行。

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

总结一下,调用 Generator 函数,返回一个遍历器对象,表明 Generator 函数的内部指针。之后,每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

一、yield 表达式

因为 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,因此其实提供了一种能够暂停执行的函数。yield表达式就是暂停标志。

遍历器对象的next方法的运行逻辑以下:

(1)遇到yield表达式,就暂停执行后面的操做,并将紧跟在yield后面的那个表达式的值,做为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)若是没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,做为返回的对象的value属性值。

(4)若是该函数没有return语句,则返回的对象的value属性值为undefined

须要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,所以等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

yield表达式与return语句区别:

类似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具有位置记忆的功能。

一个函数里面,只能执行一次(或者说一个)return语句,可是能够执行屡次(或者说多个)yield表达式。

正常函数只能返回一个值,由于只能执行一次return;Generator 函数能够返回一系列的值,由于能够有任意多个yield

二、与 Iterator 接口的关系

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

因为 Generator 函数就是遍历器生成函数,所以能够把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具备 Iterator 接口。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

上面代码中,Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具备了 Iterator 接口,能够被...运算符遍历了。

三、next 方法的参数

yield表达式自己没有返回值,或者说老是返回undefinednext方法能够带一个参数,该参数就会被看成上一个yield表达式的返回值。

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

上面代码先定义了一个能够无限运行的 Generator 函数f,若是next方法没有参数,每次运行到yield表达式,变量reset的值老是undefined。当next方法带一个参数true时,变量reset就被重置为这个参数(即true),所以i会等于-1,下一轮循环就会从-1开始递增。

四、for...of 循环

for...of循环能够自动遍历 Generator 函数时生成的Iterator对象,且此时再也不须要调用next方法。

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5

上面代码使用for...of循环,依次显示 5 个yield表达式的值。这里须要注意,一旦next方法的返回对象的done属性为truefor...of循环就会停止,且不包含该返回对象,因此上面代码的return语句返回的6,不包括在for...of循环之中。

下面是一个利用 Generator 函数和for...of循环,实现斐波那契数列的例子。

function* foo() {
      let [prev, current] = [0, 1];
      for (;;) {
        yield current;
        [prev, current] = [current, prev + current];
      }
    }

    for (let n of foo()) {
      if (n > 1000) break;
      console.log(n);
    }

Generator小案例

需求:

一、发送Ajax请求获取新闻内容

二、新闻内容获取成功再次发送请求获取对应的新闻评论内容

三、新闻内容获取失败则不须要再次发送请求。

function getNews(url) {
      $.get(url, function (data) {
        console.log(data);
        let urls = "http://localhost:3000" + data.commentUrl;
        // urls能够做为第一个yield的返回值
        // 执行第二条yeild语句,发送请求新闻评论
        // 获取的评论地址如何传入到 yield getNews(urls);靠的是第二次
        // 发送next时传入的参数,就是评论地址
        sx.next(urls);
      });
    }

    function* sendXml() {
      // 发送请求新闻内容
      let urls = yield getNews("http://localhost:3000/news?id=2");
      // 请求新闻评论内容
      yield getNews(urls);
    }

    let sx = sendXml();
    // 执行第一条yeild语句,发送请求新闻
    sx.next();

2、async

ES2017 标准引入了 async 函数,使得异步操做变得更加方便。

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

语法:

async function foo(){
  await 异步操做;
  await 异步操做;
}

特色:

一、不须要像Generator去调用next方法,遇到await等待,当前的异步操做完成就往下执行

二、返回的老是Promise对象,能够用then方法进行下一步操做

三、async取代Generator函数的星号*,await取代Generator的yield

四、语意上更为明确,使用简单,经临床验证,暂时没有任何反作用

举个栗子:

async function timeout(ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms);
      })
    }
    
    async function asyncPrint(value, ms) {
      console.log('函数执行', new Date().toTimeString());
      await timeout(ms);
      console.log('延时时间', new Date().toTimeString());
      console.log(value);
    }

    console.log(asyncPrint('hello async', 2000));

asyncPrint 执行的时候,先打印的是“函数执行”,以后进入到 timeout 函数,因为是异步执行,可是timeout未执行完成,因此 await 在等待,至关于挂起。而这一边 asyncPrint会当即返回一个 Promise对象。以后另外一边timeout、执行完成,打印出“延时时间”,以后打印“hello async”。

async函数内部return语句返回的值,会成为then方法回调函数的参数。下面代码中,函数f内部return命令返回的值,会被then方法回调函数接收到。

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

一、await 命令

正常状况下,await命令后面是一个 Promise 对象。若是不是,会被转成一个当即resolve的 Promise 对象。

resolve参数就是await的返回值。

async function f() {
  return await 123;
}

f().then(v => console.log(v))
// 123

await命令后面的 Promise 对象若是变为reject状态,则reject的参数会被catch方法的回调函数接收到。

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

二、案例:获取新闻和评论内容

async function sendXml(url) {
      return new Promise((resolve, reject) => {
        $.ajax({
          url,
          type: 'GET',
          success: data => resolve(data),
          error: error => reject(error)
        })
      })
    }

    async function getNews(url) {
      let result = await sendXml(url);
      let result2 = await sendXml(url);
      console.log(result, result2);
    }
    getNews('http://localhost:3000/news?id=2')

3、Class

JavaScript 语言中,生成实例对象的传统方法是经过构造函数。下面是一个例子。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

上面这种写法跟传统的面向对象语言(好比 C++ 和 Java)差别很大,ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,做为对象的模板。经过class关键字,能够定义类。

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

一、constructor 方法

constructor方法是类的默认方法,经过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,若是没有显式定义,一个空的constructor方法会被默认添加。

Class 的继承

Class 能够经过extends关键字实现继承,这比 ES5 的经过修改原型链实现继承,要清晰和方便不少。

class Point {
}
// ColorPoint 继承 Point
class ColorPoint extends Point {
}

上面代码定义了一个ColorPoint类,该类经过extends关键字,继承了Point类的全部属性和方法。

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,不然新建实例时会报错。这是由于子类本身的this对象,必须先经过父类的构造函数完成塑造,获得与父类一样的实例属性和方法,而后再对其进行加工,加上子类本身的实例属性和方法。若是不调用super方法,子类就得不到this对象。

相关文章
相关标签/搜索