由于本人实力不够,还是坚持继续阅读
第一章
ECMAScript7
js的新标准从提案变成正式总共经历5个阶段
- Sstage 0:strawman(展示阶段)
- Sstage 1:Propasal(征求意见阶段)
- Sstage 2:Draft(草案)
- Sstage 3:Candidate(候选阶段)
- Sstage 4:Finished(定案阶段)
第三章 解构变量
交换
提取
传入函数的参数解构
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认为他们是占两个字节。
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}
返回的对象和传入的对象是一样的值,但是对象早已不是之前的对象,用这种写法来写函数式编程应该会非常舒适。
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,这和箭头函数的核心含义是不谋而合的。
下面是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));
但是对于下面这个函数,尾递归由于全程只发生一个调用栈,复杂度为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
为了读写这段内存,需要为它指定视图。创建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的一些方法如下
set.add({})不断添加{},因为{} === {} // false
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诞生之前,异步编程大概有以下几种方案
异步
所谓异步就算将一段任务分成两段,先执行一段,然后转而执行其他的任务,等好之后,又回过头来重新执行之前的代码。
下面的就算以回调的方式实现异步代码。但是他会先 输出 123,然输出文件内容,这是典型的异步代码,但是async/await绕来绕去,原本js阻塞的同步代码被写成异步,现在async/await有将他们变成同步的 阻塞代码,醉醉醉。同步=>异步=>同步
fs.readFile('./test/index.html', 'utf-8', (err, data) => {
console.log(data);
});
console.log('123');
callback function 回调函数
我个人觉得这种渣翻译非常容易引起误解,所谓回调函数就算把第二段单独写入一个函数中,等到重写执行该函数的时候重新调用,所以callback直译过来应该是重新调用的意思,而不是所谓的回调函数。
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函数是可迭代的。
关于Promise
- then.每一个then都返回一个promise,从而实现链式调用
- finally 回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败,
- catch 捕捉错误。
Promise.all(),和Promise差不多。
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点体现
- 内置执行器。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*(){
// ...
});
}