let和const
let命令所在的代码块内有效。实际上为 JavaScript 新增了块级作用域。
以后for就用let
变量不提升,存在暂时性死区。
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
深入理解ES6
const声明不允许修改绑定,但允许修改绑定的值。
默认使用const,只有确实需要改变变量的值时使用let。因为大部分变量的值在初始化后不应再改变,而预料外的变量值的改变是很多bug的源头。
ES6 声明变量的六种方法
ES5 只有两种声明变量的方法:var命令和function命令。ES6 添加了let命令,const命令,import命令和class命令。
var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
解构
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
模式名:变量名(冒号前面是用来匹配的,如果没有冒号等价于模式名:模式名,即变量名即模式名)
用途
(1)交换变量的值(2)从函数返回多个值(3)函数参数的定义(4)提取 JSON 数据
字符串
静态字符串一律使用单引号,动态字符串使用反引号。
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
函数
rest 参数(形式为...变量名)
用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾。
// arguments变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
箭头函数
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。一个参数不需要括号。函数体只有一行代码,不需要写return。
作用1、简化;2、函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象(普通函数的this经常指向document全局变量)。减少了显式绑定this对象的写法(call、apply、bind)。(this对象的指向是可变的,但是在箭头函数中,它是固定的。)
function foo() {
setTimeout(() => {
console.log(this);
console.log('id:', this.id);
}, 2000);
}
/*function foo() {
console.log(this);//this = {id:42}
setTimeout(function(){
console.log(this);//this = window真正执行要等到 2000 毫秒后
console.log('id:', this.id);
}, 2000);
}*/
var id = 21;
foo.call({ id: 42 });
// id: 42
// 相当于ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
默认参数
明确函数的多重用途
ES5及早期版本中的函数具有多重功能,可以结合new使用,函数内的this值将指向一个新对象库函数最终会返回这个新对象。
function Person(name) {
this.name = name;
}
var person = new Person("Nicholas");
var notAPerson = Person("Nicholas");
console.log(person); //"[Object object]"
console.log(notAPerson); //"undefined"
JavaScript函数有两个不同的内部方法:[[Call]]和[[Construct]]。当通过new关键字调用函数时,执行的是[[Construct]]函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将this绑定到实例上。相当于Java的构造函数。 如果不通过new关键字调用函数,则执行[[Call]]函数,从而直接执行代码中的函数体。相当于普通函数。
深入理解ES6
let PageHandler = {
id: "123456",
init: function() {
document.addEventListener("click", function(event) {
this.doSomething(event.type); // 抛出错误
}, false);
},
doSomething: function(type) {
console.log("handing " + type + "for" + this.id);
}
};
实际上,因为this绑定的是事件目标对象的引用(在这段代码中引用的是document),而没有绑定PageHandler,且由于this.doSomething()在document中不存在,所以无法正常执行。
可以使用bind()方法显式地将函数的this绑定到PageHandler上来修正这个问题。
let PageHandler = {
id: "123456",
init: function() {
document.addEventListener("click", (function(event) {
this.doSomething(event.type);
}).bind(this), false);
},
doSomething: function(type) {
console.log("handing " + type + "for" + this.id);
}
};
为了避免创建一个额外的函数,使用箭头函数。如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this;否则,this的值会被设置为全局对象。
let PageHandler = {
id: "123456",
init: function() {
document.addEventListener("click", event => {
this.doSomething(event.type);
}, false);
},
doSomething: function(type) {
console.log("handing " + type + "for" + this.id);
}
};
数组
拓展运算符
拓展运算符可以让你指定一个数组,将它们打散后作为各自独立的参数传入函数。
在大多数使用apply()方法的情况下展开运算符可能是一个更合适的方案。
Array.from()
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
数组实例的 entries(),keys() 和 values()
for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
find()和findIndex()
如果要在数组中根据某个条件查找匹配的元素,那么find()方法和findIndex()方法可以很好地完成任务;如果只想查找与某个值匹配的元素,则indexOf()方法和lastIndexOf()方法是更好的选择。
对象
对象字面量语法拓展
属性初始值的简写
当一个对象的属性与本地变量同名时,不必再写冒号和值,简单地只写属性名即可。
function createPerson(name, age) {
return {
name,
age
}
}
对象方法的简写语法
var person = {
name: "lcf",
sayName() {
...
}
}
简写语法可以使用super关键字。
可计算属性名
let lastName = "cf"
let person = {
"first name" = "l",
[lastname] = "cf"
}
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
Symbol
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol 值通过Symbol函数生成(不用new,因为不是对象)。Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
注意,Symbol 值作为对象属性名时,不能用点运算符。用方括号。
Set和Map数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。WeakSet 的成员只能是对象。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。WeakMap只接受对象作为键名。
Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
var proxy = new Proxy(target, handler);
target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是target)进行操作。
get(target, propKey, receiver):拦截对象属性的读取,set(target, propKey, value, receiver):拦截对象属性的设置。receiver参数可选,其实是proxy实例本身。
Promise
异步编程
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果(pending(进行中)、fulfilled(已成功)和rejected(已失败))。
可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。???Promise对象提供统一的接口,使得控制异步操作更加容易。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数(接收传出来的参数)。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
所有Promise都有then()方法,它接受两个参数:第一个是当Promise的状态变为fulfilled时要调用的函数,与异步操作相关的附加数据都会传递给这个完成函数。第二个是当Promise的状态变为rejected时要调用的函数,其与完成时调用的函数类似,所有与失败状态相关的附加数据都会传递给这个拒绝函数。
完成函数和拒绝函数总是在执行器(new Promise(executer))完成后被添加到任务队列的末尾。
其实then就是一个监听器。监听Promise的状态,根据状态调用相关函数。
那怎么转换状态呢?要用resolve和reject函数。
什么时候转换状态呢?例如fs.readFile(),要在成功读取文件数据完毕后将Promise状态从pending变为fulfilled,即在callback函数resolve(result)

step1:主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
step2: 主线程遇到异步任务,指给对应的异步进程进行处理(WEB API);
step3: 异步进程处理完毕(Ajax返回、DOM事件处理、Timer到等),将相应的异步任务推入任务队列;
step4: 主线程执行完毕,查询任务队列,如果存在任务,则取出一个任务推入主线程处理(先进先出);
step5: 重复执行step2、3、4;称为事件循环。
其中的异步进程有:
a、类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中;
b、setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中;
c、Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。
回调函数是事件完成后才被添加到任务队列中的,异步事件由各自的异步模块处理和决定是否完成。队列就是先进先出,哪个事件先完成其回调函数越早被调用。
Iterator
遍历器对象本质上,就是一个指针对象。
指针对象的next方法,用来移动指针。开始时,指针指向数组的开始位置。然后,每次调用next方法,指针就会指向数组的下一个成员。
for...of循环
循环对象属性 for...in
遍历数组 for...of
for...in循环出的是key,for...of循环出的是value
优点:可以保证输出顺序,能与break、continue、return配合使用
Generator
Generator 函数是一个状态机,封装了多个内部状态。还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。
调用遍历器对象的next方法,使得指针移向下一个状态。可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
next()、throw()、return()这三个方法本质上是同一件事,它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。
Generator 函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。(Ajax)
Generator函数的异步应用
异步操作需要暂停的地方,都用yield语句注明。value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
Thunk函数在JavaScript里是一个只接受回调函数作为参数的单参数函数。
yield命令用于将程序的执行权移出 Generator 函数,那么就需要一种方法,将执行权再交还给 Generator 函数。
这种方法就是 Thunk 函数,因为它可以在回调函数里,将执行权交还给 Generator 函数。
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);//next方法还可以接受参数,向 Generator 函数体内输入数据
if (result.done) return;
result.value(next); //result.value其实是个Thunk函数,参数是一个回调函数
}
next();//递归函数
}
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
var g = function* (){
var r1 = yield readFileThunk('/etc/fstab'); //执行权将交给其他协程
console.log(r1.toString());//r1得到data
var r2 = yield readFileThunk('/etc/shells');
console.log(r2.toString());
};
run(g);
深入理解ES6
function *createIterator() {
let first = yield 1;
let second = yield first + 2;
yield second + 3;
}
let iterator = createIterator();
每次yield前正在执行的语句
iterator.next().value; // 1 ...yield 1;
iterator.next(4).value; // 6 let first = ...
...yield first + 2;
iterator.next(5).value; // 8 let second = ...
yield second + 3;
iterator.next().value; // undefined
异步操作
// like fs.readFile
function fetchData() {
// ...
// doSomething
// ...
// like
let a
for(i = 0; i < 1000000000; i++) {
a += i
}
// complete
return function(callback) {
// err result
callback(null, "Hi!")
}
}
fetchData()(function(err, result) {
console.log(result)
})
只有fetchData完成所有操作(读文件,读数据库等)才会返回一个可以执行回调过程的函数。回调函数接收fetchData已读取的数据作为参数,意思是你可以在回调函数操作这些数据。
执行异步操作的传统方式
fs.readFile("./config.json", function(err, contents) {
if(err) {
throw err;
}
doSomethingWith(contents)
}
用同步代码执行异步操作
这里的yield作用是暂停run函数执行,直到readFile返回结果(什么时候返回结果由任务执行器决定)
像node.js中的fs.readFile和mongodb中的db.users.find函数返回的结果就是异步调用函数中的result参数。
run(function *() {
let contents = yield readFile("./config.json")
doSomethingWith(contents)
})
async函数
async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,它就是 Generator 函数的语法糖。
async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。
(1)内置执行器 所有koa2的 await next()要括号
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
class
模块
模块真正的魔力所在是仅导出和导入你需要的绑定,而不是将所用东西都放到一个文件。
导出默认值export default意义是不用知道模块导出函数具体名字,只需赋值给自己创建的对象即可导入。
let和const
let命令所在的代码块内有效。实际上为 JavaScript 新增了块级作用域。
以后for就用let
变量不提升,存在暂时性死区。
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
深入理解ES6
const声明不允许修改绑定,但允许修改绑定的值。
默认使用const,只有确实需要改变变量的值时使用let。因为大部分变量的值在初始化后不应再改变,而预料外的变量值的改变是很多bug的源头。
ES6 声明变量的六种方法
ES5 只有两种声明变量的方法:var命令和function命令。ES6 添加了let命令,const命令,import命令和class命令。
var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
解构
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
模式名:变量名(冒号前面是用来匹配的,如果没有冒号等价于模式名:模式名,即变量名即模式名)用途
(1)交换变量的值(2)从函数返回多个值(3)函数参数的定义(4)提取 JSON 数据
字符串
静态字符串一律使用单引号,动态字符串使用反引号。
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
函数
rest 参数(形式为...变量名)
用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾。
箭头函数
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。一个参数不需要括号。函数体只有一行代码,不需要写return。
作用1、简化;2、函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象(普通函数的this经常指向document全局变量)。减少了显式绑定this对象的写法(call、apply、bind)。(this对象的指向是可变的,但是在箭头函数中,它是固定的。)
默认参数
明确函数的多重用途
ES5及早期版本中的函数具有多重功能,可以结合new使用,函数内的this值将指向一个新对象库函数最终会返回这个新对象。
function Person(name) {
this.name = name;
}
var person = new Person("Nicholas");
var notAPerson = Person("Nicholas");
console.log(person); //"[Object object]"
console.log(notAPerson); //"undefined"
JavaScript函数有两个不同的内部方法:[[Call]]和[[Construct]]。当通过new关键字调用函数时,执行的是[[Construct]]函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将this绑定到实例上。相当于Java的构造函数。 如果不通过new关键字调用函数,则执行[[Call]]函数,从而直接执行代码中的函数体。相当于普通函数。
深入理解ES6
实际上,因为this绑定的是事件目标对象的引用(在这段代码中引用的是document),而没有绑定PageHandler,且由于this.doSomething()在document中不存在,所以无法正常执行。
可以使用bind()方法显式地将函数的this绑定到PageHandler上来修正这个问题。
为了避免创建一个额外的函数,使用箭头函数。如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this;否则,this的值会被设置为全局对象。
数组
拓展运算符
拓展运算符可以让你指定一个数组,将它们打散后作为各自独立的参数传入函数。
在大多数使用apply()方法的情况下展开运算符可能是一个更合适的方案。
Array.from()
实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。
数组实例的 entries(),keys() 和 values()
for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
find()和findIndex()
如果要在数组中根据某个条件查找匹配的元素,那么find()方法和findIndex()方法可以很好地完成任务;如果只想查找与某个值匹配的元素,则indexOf()方法和lastIndexOf()方法是更好的选择。
对象
对象字面量语法拓展
属性初始值的简写
当一个对象的属性与本地变量同名时,不必再写冒号和值,简单地只写属性名即可。
对象方法的简写语法
简写语法可以使用super关键字。
可计算属性名
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
Symbol
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol 值通过Symbol函数生成(不用new,因为不是对象)。Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
注意,Symbol 值作为对象属性名时,不能用点运算符。用方括号。
Set和Map数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。WeakSet 的成员只能是对象。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。WeakMap只接受对象作为键名。
Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
var proxy = new Proxy(target, handler);target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是target)进行操作。
get(target, propKey, receiver):拦截对象属性的读取,set(target, propKey, value, receiver):拦截对象属性的设置。receiver参数可选,其实是proxy实例本身。
Promise
异步编程
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果(pending(进行中)、fulfilled(已成功)和rejected(已失败))。
可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。???Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数(接收传出来的参数)。
所有Promise都有then()方法,它接受两个参数:第一个是当Promise的状态变为fulfilled时要调用的函数,与异步操作相关的附加数据都会传递给这个完成函数。第二个是当Promise的状态变为rejected时要调用的函数,其与完成时调用的函数类似,所有与失败状态相关的附加数据都会传递给这个拒绝函数。
完成函数和拒绝函数总是在执行器(new Promise(executer))完成后被添加到任务队列的末尾。
其实then就是一个监听器。监听Promise的状态,根据状态调用相关函数。
那怎么转换状态呢?要用resolve和reject函数。
什么时候转换状态呢?例如fs.readFile(),要在成功读取文件数据完毕后将Promise状态从pending变为fulfilled,即在callback函数
resolve(result)step1:主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
step2: 主线程遇到异步任务,指给对应的异步进程进行处理(WEB API);
step3: 异步进程处理完毕(Ajax返回、DOM事件处理、Timer到等),将相应的异步任务推入任务队列;
step4: 主线程执行完毕,查询任务队列,如果存在任务,则取出一个任务推入主线程处理(先进先出);
step5: 重复执行step2、3、4;称为事件循环。
其中的异步进程有:
a、类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中;
b、setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中;
c、Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。
回调函数是事件完成后才被添加到任务队列中的,异步事件由各自的异步模块处理和决定是否完成。队列就是先进先出,哪个事件先完成其回调函数越早被调用。
Iterator
遍历器对象本质上,就是一个指针对象。
指针对象的next方法,用来移动指针。开始时,指针指向数组的开始位置。然后,每次调用next方法,指针就会指向数组的下一个成员。
for...of循环
循环对象属性 for...in
遍历数组 for...of
for...in循环出的是key,for...of循环出的是value
优点:可以保证输出顺序,能与break、continue、return配合使用
Generator
Generator 函数是一个状态机,封装了多个内部状态。还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。
调用遍历器对象的next方法,使得指针移向下一个状态。可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
next()、throw()、return()这三个方法本质上是同一件事,它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。
Generator 函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。(Ajax)
Generator函数的异步应用
异步操作需要暂停的地方,都用yield语句注明。value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
Thunk函数在JavaScript里是一个只接受回调函数作为参数的单参数函数。
yield命令用于将程序的执行权移出 Generator 函数,那么就需要一种方法,将执行权再交还给 Generator 函数。
这种方法就是 Thunk 函数,因为它可以在回调函数里,将执行权交还给 Generator 函数。
深入理解ES6
异步操作
只有fetchData完成所有操作(读文件,读数据库等)才会返回一个可以执行回调过程的函数。回调函数接收fetchData已读取的数据作为参数,意思是你可以在回调函数操作这些数据。
执行异步操作的传统方式
用同步代码执行异步操作
这里的yield作用是暂停run函数执行,直到readFile返回结果(什么时候返回结果由任务执行器决定)
像node.js中的fs.readFile和mongodb中的db.users.find函数返回的结果就是异步调用函数中的result参数。
async函数
async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,它就是 Generator 函数的语法糖。
async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。
(1)内置执行器 所有koa2的 await next()要括号
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
class
模块
模块真正的魔力所在是仅导出和导入你需要的绑定,而不是将所用东西都放到一个文件。
导出默认值export default意义是不用知道模块导出函数具体名字,只需赋值给自己创建的对象即可导入。