Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

es6标准入门-阅读笔记 #23

Open
plh97 opened this issue Apr 8, 2018 · 0 comments
Open

es6标准入门-阅读笔记 #23

plh97 opened this issue Apr 8, 2018 · 0 comments
Assignees
Labels
博客 写一些前端技术记录 看书 其实如果不看书的话,那么每天写的东西都和昨天一样,又有什么意思

Comments

@plh97
Copy link
Owner

plh97 commented Apr 8, 2018

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

第一章

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语言
int8 1 8位带符号整数 signed char
Uint8 1 8位不带符号整数 unsigned char
Uint8C 1 8位不带符号整数 unsigned char
Int16 2 16位带符号整数 short
Uint16 2 16位不带符号整数 unsigned short
Int32 4 32位带符号整数 int
Uint32 4 32位不带符号整数 unsigned int
Float32 4 32位浮点数 float
Float64 8 64位浮点数 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*(){
        // ...
    });
}
@plh97 plh97 added 博客 写一些前端技术记录 看书 其实如果不看书的话,那么每天写的东西都和昨天一样,又有什么意思 labels Apr 8, 2018
@plh97 plh97 self-assigned this Apr 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
博客 写一些前端技术记录 看书 其实如果不看书的话,那么每天写的东西都和昨天一样,又有什么意思
Projects
None yet
Development

No branches or pull requests

1 participant