es6标准入门-阅读笔记

avatarplhDigital nomad
由于本人实力不够,还是坚持继续阅读

第一章

ECMAScript7

js的新标准从提案变成正式总共经历5个阶段

  • Sstage 0:strawman(展示阶段)
  • Sstage 1:Propasal(征求意见阶段)
  • Sstage 2:Draft(草案)
  • Sstage 3:Candidate(候选阶段)
  • Sstage 4:Finished(定案阶段)

第三章 解构变量

交换

[a,b] = [b,a]

提取

let {a,b,c} = json;

传入函数的参数解构

function func({a,b}){}
func({a:1,b:2});

传入函数参数自带默认值

function func(json={
  a:1,
  b:2
}){
  return true;
}

第四章 字符串的扩展

js允许采用\uxxx的形式表示一个字符,其中xxxx表示字符串码点。 "\u0061" // "a" 但是这种表示法只限于\u0000 ----\uFFFF之间的字符。超出这个范围的必须用两个字节表示 "\uD842\uDFB7" \"𠮷" ES6对此进行了改进 "\u{42}\u{43}\u{44}" JavaScript内部,字符串以UTF-16的形式储存,每个字符固定2个字节,对于那些需要四个字节储存的字符,JavaScript认为他们是占两个字节。 image ES6提供codePointAt(0)的方法,能正确处理4个字节的字符,并返回一个字符的码点。

padStart padEnd

ES7推出字符串不全长度功能,如果字符串长度未达到,会自动在头尾补全, 'x'.padStart(5,'ab') // 'ababx'; 'x'.padEnd(4,'ab') // 'xaba';

4.10 模板字符串

`
    hi,i am crazy
    hihih${ iamchangevalue }
`

4.11 实例:模板编译

下面例子,一个通过模板字符串生成真正正式模板的实例 各种奇淫技巧我就不一一详述。

第八章 函数的扩展

写代码经常遇到一个这样的问题,他没有forEach的方法,我们运用扩展符号,轻松将他转为数组。

var list = document.querySelectorAll('div');
var arr = [...list];

a.name即返回函数名字

a = function func(a){}
a.name // 'func'

构造函数的名字

(new Function).name    // "anonymous"

bind绑定的函数的名字,返回的是 'bound '

a = function func(a){}
a.bind({}).name    // "bound func"

8.5 箭头函数(关键)

var func = () => {};   // 这是最常见的箭头函数,这里不再复述

当返回的是一个对象的时候呢??意想不到的是,报错了,因为函数默认识别{作为一段代码块,而你则把{当作对象的开头,所以这种情况下必须加上小括号。(),同时箭头函数可以配合变量结构一起使用,相当让人愉悦的一段代码。

var getObject = ({a,b}) => {a:1,b:2}

image 返回的对象和传入的对象是一样的值,但是对象早已不是之前的对象,用这种写法来写函数式编程应该会非常舒适。

let obj = ({a,b}) => ({a,b});
obj({a:1,b:3});
[1,2,3].map(function(e){
  return e*e;
})
[1,2,3].map(e=>e*e);

总结一下,箭头函数最主要的效果是简化回调函数,但这需要建立在对函数熟悉的基础上。但是使用过程中要注意以下几点:

  • 1.函数体内的this对象就算定义时所在的对象,而不是使用时所在的对象。这看起来比普通函数要合理的多。。我就爱用箭头函数。
function functhis() {
  return this;
}
const arrowthis = () => this;
console.log(functhis()); // window
console.log(arrowthis()); // {}
const a = function() {
  console.log(functhis()); // window
  console.log(arrowthis()); // {}
};
a();
  • 不能当作构造函数,不然会报错。不可以使用new命令,这看起来也是比较合理的。
function afunc(){}   // undefined
new afunc()
afunc {}
afunc = () => {}
() => {}
new afunc()          // VM19499:1 Uncaught TypeError: afunc is not a constructor   .at <anonymous>:1:1
  • 不能使用arguments对象,该对象在函数存在,但是你在里面拿不到参数
  • 不能使用yield命令,因此箭头函数不能用作Generator函数。

call和普通调用不同,call内传入的参数是就算函数内的this,而此处箭头函数,this永远指向函数本身。而如果是普通函数,this指向window,严格模式下,this指向undefined.,不错的es6。他让js变得简单优雅。。。

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}
foo();
foo.call({ id: 1 });

下面代码,this永远指向函数本身,因为他是箭头函数。

const handler = {
  id: '123456',
  init() {
    document.addEventListener(
      'click',
      e => this.dosomething(e.type), false,
    );
  },
  dosomething(type) {
    console.log(`handle ${type} for ${this.id}`);
  },
};

这里定时器内部箭头函数的this同样指向对象本身

function Timer() {
  this.sec = 0;
  setInterval(() => this.sec++, 100);
}
const timer = new Timer();
setInterval(() => {
  console.log(timer.sec);
}, 300);

this指向的固定化,为什么呢,因为箭头函数内部根本没有this,所以this永远指向对象本身。正因为如此,所以箭头函数同样不能做构造函数。

函数绑定

箭头函数可以用以将this指向函数本身,这大大简便了js绑定,举个例子react官方说的一个绑定例子

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

react采用this.handleClick.bind(this),这样的绑定函数会返回一个函数,而返回的新函数通过bind绑定的新函数,下面看mdn对于bind的定义。返回的新函数this永远指向,意思就是,bind函数传入的第一个参数,就算新函数的this,无论你怎么调用,都是这个this,这和箭头函数的核心含义是不谋而合的。 image 下面是react官方的第二种写法,省区了bind步骤,就是因为使用了箭头函数。

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

8.6 函数绑定

箭头函数可以绑定this,大大减少了显示绑定this对象的方法(call,apply,bind),但是es7提出了新的提案,::用来绑定

fo0::bar
// 等同于
bar.bind(foo)

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

8.7 尾调用优化

说的就算高阶函数

let highFunc = (arr) => {
  let a = 1;
  let b = 2;
  return g(a+b);
}

js的函数可以说是非常灵活的可以在任何地方被调用,包括if(内部){},关键不在于函数书写形式,关键在于所返回的是函数还是对象还是字符串,或者根本没有return,func()()()只要返回的是所需要的东西即可,不管你后面跟了几个括号。

尾调用极其不同的地方在于其特殊的调用函数的位置。

具体可以了解相关调用栈(call stack #17 )由于函数尾部是一个新的函数,所以调用它的函数会被先存入调用栈,等待新函数被找到之后,新函数也会被放入调用栈,然后调用栈按照FILO(先入后出)队列原则,依次执行。

let highFunc = (arr) => {
  let a = 1;
  let b = 2;
  return g(a+b);
}
highFunc()
// 等价于
function highFunc(){
  return g(3);
}
// 等价于
g(3);

上面代码,如果g不是尾调用,函数f就需要保存内部变量m,n,以及g的调用位置,。但是当g被调用之后,函数f就结束了,所以f(x)函数完全可以被删除。 上面说的就是尾调用优化,可以节省部分内存??

尾递归

函数调用自身称之为递归,在尾部调用自身,称之为尾递归, 下面的是递归函数,调用栈最大承受量为11370次,容易发生栈溢出效应。其复杂度为O(n)

function factorial(n) {
	if (n === 1) return 1;
  return n + factorial(n - 1);
}
console.log(factorial(11370));

image 但是对于下面这个函数,尾递归由于全程只发生一个调用栈,复杂度为O(1),当然此刻依然是调用栈溢出,因为只是es6对尾递归进行了优化,你需要开启chrome实验室新功能。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n + total);
}
console.log(factorial(11300, 1));

第十一章 Proxy和Reflect

proxy 概述

proxy用于修改某些操作的默认行为,等同于在语言层面做一些事,所以这属于元编程。,即对语言进行编程。

const obj = new Proxy({}, {
  get(target, key, receiver) {
    console.log(`getting ${key}`);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log(`setting ${key}`);
    return Reflect.get(target, key, value, receiver);
  },
});

上面的代码对一个空对象做了一层拦截层,重定义了读取属性的set和设置set行为。这里暂时先不解释其具体做法。看看运行结果。

obj.count = 1;    // setting count
++obj.count;     // setting count, getting count

12.二进制数组

ArrayBuffer对象,TypedArray,DataView视图,是JavaScript操纵二进制数据的一个接口, 二进制数组由三个对象组成

  • 1.ArrayBuffer 对象:代表内存中的一段二进制数据,可以通过视图进行操作。视图部署了数组接口,这意味着,可以用数组的方法操作内存。(简单来说代表了原始的二进制数据)
  • 2.TypedArray视图:共包括9中类型的视图,比如Uint8Array(无符号8位整数)数组视图`,(用于读/写简单类型的二进制数据)
  • 3.DataView视图:可以自定义复合格式的视图,(用于读/写复杂类型的二进制数据)
数据类型字节长度含义对应c语言
int818位带符号整数signed char
Uint818位不带符号整数unsigned char
Uint8C18位不带符号整数unsigned char
Int16216位带符号整数short
Uint16216位不带符号整数unsigned short
Int32432位带符号整数int
Uint32432位不带符号整数unsigned int
Float32432位浮点数float
Float64864位浮点数double

很多浏览器API用到了二进数组操作二进制数据,下面是其中的几个。

  • File API
  • XMLHttpRequest
  • Fetch API
  • Canvas
  • Websockets

12.1 ArrayBuffer 对象

概述

ArrayBuffer对象代表储存二进制数据的一段内存,他不能直接读/写,只能通过视图来读写。视图的作用是指以指定格式解读二进制数据。 ArrayBuffer也是一个构造函数,可以分配一段可以存放的数据的内存区域。 下面这段代码生成了一段32字节的内存区域,每个字节的默认值都是0,可以看到ArrayBuffer构造函数的参数是所需要的内存大小(单位位字节)。

var buf = new ArrayBuffer(32);
buffer
var buf = new ArrayBuffer(12);
buffer

image 为了读写这段内存,需要为它指定视图。创建DataView视图。需要提供ArrayBuffer对象实例作为参数

var buf = new ArrayBuffer(32);
dataView = new DataView(buffer); 
dataView.getInt8(16)    // 0

上面代码对一段32字节的内存建立DataView视图,然后以不带符号的8位整数格式读取第16个元素得到0,因为他的所有都是0。

12.2 TypedArray视图

ArrayBuffer对象作为内存区域存放多种类型数据。同一段内存不同数据的不同解读方式,这就叫做视图。ArrayBuffer存在2种视图,一种是TypedArray视图,另一种是DataView视图,前者所有数组成员都是同一个数据类型吗,后者数组成员可以是不同的数据类型。

13.章 set 和map

set作为es6新的一种数据结构,类似于数组,但是他的成员的值是唯一的。没有重复的值。 set的一些方法如下 image set.add({})不断添加{},因为{} === {} // false image

WeakSet

和set类,但不同之处在于

  • 1.只能添加对象
  • 2.弱引用,如果其他对象不再引用,它会自动启用垃圾回收机制。同时他是不可遍历的。

Map

map结构的目的和基本用法。

插一道面试题。

目前存在3个ajax访问,当url1和url2同时访问某个接口,只有当两个接口同时拿到数据的时候,url3开始执行去拿某个接口的数据。url1,和url2拿数据的速度未知。可能url1更快,也可能url2更快。只有当2个接口的数据都拿到之后才去拿url3的数据。闲话不多说,上代码

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    console.log('p1');
    resolve();
  }, 2000);
});
const p2 = new Promise((resolve) => {
  setTimeout(() => {
    console.log('p2');
    resolve();
  }, 3000);
});
const p3 = new Promise((resolve) => {
  setTimeout(() => {
    console.log('p3');
    resolve();
  }, 1000);
});
Promise.all([p1, p2]).then(() => {
    console.log('values');
    p3;
});

14章lterator 和 for循环

遍历器(lterator)就是这样一种接口机制,为各种不同的接口机制提供统一的访问机制。任何数据结构只要有lterator接口,就能遍历循环。 lterator的作用有三种,

  • 为各种数据提供统一的简便的访问接口,
  • 使得数据结构的成员依靠某种次序依次访问。
  • es6创造了一种新的遍历命令------for...of循环

lterator的遍历过程是这样的,

  • 1.创建一个指针对象,指向当前数据结构的起始位置。也就是说遍历器对象本质上就算一个指针对象。
  • 2.第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  • 3.第二次调指针对象,指针就指向数据结构的第二个成员。
  • 4.不断调用指针对象的next方法,知道他指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构当前成员的信息。下面定义一个makeIterator函数用来遍历循环

function makeIterator(array) {
  let nextIndex = 0;
  return {
    next() {
      return nextIndex < array.length ? {
        value: array[nextIndex += 1], done: false,
      } : {
        value: undefined, done: true,
      };
    },
  };
}
const itt = makeIterator(['a', 'b', 'b', 'b', 'b', 'b', 'b']);
itt.next()   // { value: 'b', done: false }
itt.next()   // { value: 'b', done: false }
itt.next()   // { value: 'b', done: false }
itt.next()   // { value: 'b', done: false }

14.3 调用Iterator调用的默认场合

[...new Array(3)]    // [undefined,undefined,undefined]
[...'23r3r23r']        // ['a','b'....]

还有菊花函数 function* (){ yield [1,2,3,4,5] }

const generator = function* () {
  yield 1;
  yield* [2, 3, 4];
  yield 5;
};
const iterator = generator();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
======得到的值======
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: 5, done: false }
{ value: undefined, done: true }

上面的[]数组内部内容被自动解构,他后面如果跟的是一个可遍历的结构,他会被用于遍历器的接口。

其他场合
  • for...of
  • Array.from()
  • Map()。Set()。WeakSet()。
  • Promise.all()
  • Promise.race()

14.4 字符串的Iterator接口 字符串本身其实也可以遍历,他也具有遍历接口

str = '23r23r'[Symbol.iterator]();
str.next();  // {value: "2", done: false}
str.next();  // {value: "3", done: false}
str.next();  // {value: "r", done: false}
str.next();  // {value: "2", done: false}
str.next();  // {value: "3", done: false}
str.next();  // {value: "r", done: false}
str.next();  // {value: undefined, done: true}
[...'wefe233']  // ["w", "e", "f", "e", "2", "3", "3"]

14.6 遍历器对象的return()。throw()

Set 和 Map 结构

Set 和 Map结构具有Iterator接口,可以直接使用for...of循环

const es6 = new Map();
es6.set('eee',1);
es6.set('fff',1);
es6.set('ggg',1);
es6.set('hhh',1);
es6.set('iii',1);
for(var [name,value] of es6){
    console.log(name,value)
}
// eee 1
// fff 1
// ggg 1
// hhh 1
// iii 1

16 章 Promise

下面then会生成2个promise,先new一个promise,then里面再生成一个新的promise,通过不断的生成一个新的promise,而产生链式调用,这个和jQuery是不谋而合的。

const getJsono = new Promise((resolve, reject) => {
  resolve(x + 1);
});
getJsono
    .then((json) => {
	console.log(json);
    })
    .catch((err) => {
	console.log(err);
    });

promise.all

多个rpomise一起完成。 在进行下一步。 下面请看async/await,虽然他也只是基于Generator的语法糖,但是配合promise少不了它。

17章 async和异步操作。

异步编程对于js这门语言非常重要。js是单线程,如果没有异步,就会卡死。 es诞生之前,异步编程大概有以下几种方案

  • 回调函数
  • 事件监听
  • 发布订阅
  • promise函数

异步

所谓异步就算将一段任务分成两段,先执行一段,然后转而执行其他的任务,等好之后,又回过头来重新执行之前的代码。 下面的就算以回调的方式实现异步代码。但是他会先 输出 123,然输出文件内容,这是典型的异步代码,但是async/await绕来绕去,原本js阻塞的同步代码被写成异步,现在async/await有将他们变成同步的 阻塞代码,醉醉醉。同步=>异步=>同步

fs.readFile('./test/index.html', 'utf-8', (err, data) => {
    console.log(data);
 });
console.log('123');

callback function 回调函数

我个人觉得这种渣翻译非常容易引起误解,所谓回调函数就算把第二段单独写入一个函数中,等到重写执行该函数的时候重新调用,所以callback直译过来应该是重新调用的意思,而不是所谓的回调函数。 image

promise

回调函数本身并没有问题,问题在于多个回调函数嵌套,假定读取A文件后再读取B文件,代码如下

fs.readFile('./test/index.html', 'utf-8', (err, data) => {
	fs.readFile('./test/index.html', 'utf-8', (err, data1) => {
		console.log(data, data1);
	});
});

为了避免多重嵌套,所以出现了异步函数。然而我觉得这个好失败。怪不得TJ大神要转去玩GO, 17.2 Generator 函数

协程

传统的编程语言早已有异步解决方案,(其实是多任务的解决方案)。其中有一种叫做协程(coroutine),意思是多个线程相互协作,完成异步任务。 协程有点像函数,又有点像线程,流程如下:

  • 第1步,协程A开始执行
  • 第2步,协程A执行到一半,暂停,执行权交给B去执行。
  • 第3步,一段时间后,暂停,协程B交换执行权。
  • 第4步,协程A恢复执行。

上面的协程A就算异步任务。因此他分成2段执行。

Generator函数的概念

他会将函数执行权交出去,即暂停执行。需要暂停的地方用yield,

function* gen(x){
    var y = yield x+2;
    return y;
}
var g = gen(1);
g.next();    // {value:3,done,false}
g.next();    // {value:undefined,done,true}

上述代码,调用Generator函数会返回一个内部指针,g,这是生成器函数不同于普通函数的地方,他返回的指针每次调用next都会指向下一个yield,也就是返回一个对象。 Generator可以暂停执行,这是它可以封装异步函数的根本原因。

关于Generator中的 then,他返回的是什么??,async返回的then呢?

  • next 返回下一个数
  • return 推出迭代
  • throw 返回错误
  • Symbol(Symbol.iterator) 说明构造器Generator函数是可迭代的。 image

关于Promise

  • then.每一个then都返回一个promise,从而实现链式调用
  • finally 回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败,
  • catch 捕捉错误。 image
Promise.all(),和Promise差不多。

image

17.3 Thunk 函数

参数的求值策略

传入的是一个函数,那么传入的函数等到传入的一刻才计算。尾递归就是一个例子,但是尾递归存在栈溢出的性能问题。

17.4 co模块

#####基本用法 co模块是TJ发布的一个小工具。用于generator函数自动执行。

var gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

co模块可以让你不用编写Generatar函数的执行器。

var co = require('co');
co(gen);

上面的代码中,Generator函数只要传入co函数就会自动执行。 co函数返回一个Promise对象,因此可以用then方法添加回调函数。

co(gen).then(function(){
  console.log('Generator 函数执行完成');
})

上面的代码中,等到Generator函数执行结束,就会输出一行提示。 ####co模块的原理 为什么co模块可以自动执行Generator函数? 前面说过,Generator就是一个异步执行函数的容器。它自动执行需要一种机制,当异步操作有了结果能够自动交回执行权。

  • 1.回调函数。将异步操作包装成Thunk函数,在回调函数中交回执行权。
  • 2.Promise对象。将异步操作包装成promise对象,用then方法交回执行权。

co模块其实就是将两种自动执行器(Thunk函数和Promise对象)包装成了一个模块。使用co的前提条件是,Generator函数的yield命令后面只能是Thunk函数或Promise对象。 下面讲一下基于generator函数的自动执行器。这是理解co函数的关键。 ####基于Promise对象的自动执行 还是沿用上面的例子。首先把fs模块的readFile方法包装成一个Promise对象。

const readFile = fileName => new Promise(((resolve, reject) => {
  fs.readFile(fileName, (err, data) => {
    if (err) reject(err);
    resolve(data);
  });
}));
const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
const g = gen();
g.next().value.then((data) => {
  g.next().value.then((data) => {
    g.next(data);
  });
});

手动执行的方法其实就算用then方法层层添加回调函数,理解这一点,就能写出自动执行函数,

function run(gen) {
  const g = gen();
  function next(data) {
    const result = g.next(data);
    if (result.done) return result.value;
    result.value.then((data) => {
      next(data);
    });
  }
}

上面的代码中,只要Generator函数还没执行到最后异步,next函数就调用自身以实现自动执行。 ####co模块的源码 co就是上面这个自动执行的扩展,他的源码只有十几行,非常简单。 首先,co函数接受Generator函数作为参数,返回一个Promise对象。

function co(gen){
  var ctx = this;
  return new Promise(function(resolve,reject)=>{})
}

在返回Promise对象中,co先检查参数gen是否为Generator函数。如果是,就执行该函数,得到内部指针对象;如果不是就返回,并将promise对象的状态改为Resolve。

function co(gen) {
  const ctx = this;
  return new Promise((resolve, reject) => {
    if (typeof gen === 'function') gen = gen.call(ctx);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFullfilled(res);
    function onFullfilled(res) {
      let ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
  });
}

最后最关键的就算next函数

function next(ret) {
  if (ret.done) return resolve(ret.value);
  const value = toPromise.call(ctx, ret.value);
  if (value && isPromise(value)) return value.then(onFullfilled, onRejected);
  return onRejected(new TypeError(`You may only yield a function, promise, generator, array, object, but not the following object was passed: ${String(ret.value)}"`));
}

上面代码中,next函数内部的代码一共只有4行命令。 第一行,检查当前是否为Generator函数最后一步,如果是就返回。 第二行,确保每一步的返回值是Promise对象。 第三行,使用then方法为返回值加上回调函数,然后通过onFullfilled函数再次调用next函数。 第四行,在参数不符合要求的情况下,将promise对象状态改为Rejected,从而终止执行,

并发处理异步操作

co支持并发的异步操作,即允许某些操作同时进行,等到他们完成,才进行下一步。这时要把并发的操作都放在数组或者对象里面,跟在yield语句后面。

// 数组写法
co(function* () {
  const res = yield [
    Promise.resolve(1),
    Promise.resolve(2),
  ];
});

// 对象写法
co(function* () {
  const res = yield {
    0: Promise.resolve(1),
    0: Promise.resolve(2),
  };
});

// 下面是一个例子
co(function* (){
	var values = [n1,n2,n3];
	yield values.map(somethingAsync);
})

function* somethingAsync(x){
	// do something async
	reyurn y;
}

上面代码允许并发3个somethingAsync异步操作,等到他们全部完成,才能进行下一步。

17.5 async 函数

他就是Generator函数的语法糖 前文有一个Generator函数,一次读取2个文件

const readFile = fileName => new Promise(((resolve, reject) => {
  fs.readFile(fileName, (err, data) => {
    if (err) reject(err);
    resolve(data);
  });
}));
const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
//写成async函数就是下面这样
const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

其实就只是将*号换成async,将yield换成await的语法糖。 async函数对Generator函数进行了改进,以下4点体现

  1. 内置执行器。Generator必须要有执行器,而async内置了执行器。所以有了co模块,而async自带执行器,async函数写法与普通函数一样只要一行。
var result = asyncReadFile();

2.上面代码调用了asyncReadFile函数,然后他就会自动执行,输出结果,完全不想Generator函数需要调用next方法,或者co模块,才能执行。 3.更好的语义。async和await比起yield语义更加清楚。 4.更广的适用性,co模块约定,yield命令后面只能是Thunk函数或者promise对象,而async函数的await后面能是promise对象和原始类型的值,无论数字对象字符串等。 5.返回值Promise。async函数的返回值是Promise对象,这比Generator函数的返回值是Lterator对象方便多了,你完全可以用then方法指定下一步。 进一步说明,async函数完全可以看作由多个异步操作包装成一个Promise对象,而await命令就算内部then语法糖。

async函数的实现

async函数的实现的实现就是把Generator和自动执行函数包在一个函数中。

async function fn(args){
    // ....
}
// 等同于
function fn(args){
    return spawn(function*(){
        // ...
    });
}