ES6前沿特性:生成器和promise
生成器
1. 生成器的概念和迭代器
用途:使用生成器来简化复杂循环,使用其能力还挂起和恢复循环,写出更简单、优雅的异步代码
概念:生成器函数(generator)能生成一组值的序列,但每个值的生成是基于每个yield
显示请求新值,随后生成器要么响应一个新值,要么告诉我们它不会再生成新值。生成器每次生成一个新值后,都会更新为挂起状态,然后等待另一个值的请求到来后,生成器会从上次离开的位置重新执行。
// 使用生成器生成值
function * wea(){ // function后面添加 * 定义生成器函数
yield 3; // yield 显示的请求值
yield 9; // 注意生成器函数没有 return返回
}
//使用新的for of循环类型遍历新值
for(let value of wea()){ // wea()调用生成器函数并不会执行该生成器,会返回迭代器对象
console.log(value)
}
//使用迭代器对象控制生成器
const weaObj = wea();
const res1 = weaObj.next();
console.log('res1:',res1); // {value: 3, done: false}
const res2 = weaObj.next();
console.log('res2:',res2); // {value: 9, done: false}
const res3 = weaObj.next();
console.log('res3:',res3); // {value: undefined, done: true}
//调用生成器函数返回迭代器对象,该对象暴露一个next方法,向生成器请求一个新值
//next函数调用后,生成器开始执行代码,碰到yield会生成一个中间结果(生成值序列中的一个),然后返回对象,包含生成值和是否结束。
//每当生成一个当前值后,生成器会非阻塞的挂起执行,等待下一次值请求的到来
//随后通过next的再次调用,再次向生成器请求一个值,该生成器会从挂起状态唤醒,中断的生成器从上次离开的位置继续执行代码,直到碰到yield生成中间结果,再次挂起。
//当next调用后,没有更多可执行代码了,返回结果对象,{value: undefined, done: true}
//对迭代器进行迭代
const weaObj1 = wea();
let item;
//判断迭代器next方法返回的对象的done属性,只要不为false,就一直循环遍历该迭代器的next
//这也是for of的原理,for of是迭代的语法糖
while(!(item = weaObj1.next()).done){
console.log(item);
}
// 把执行权交给下一个生成器
function * Generator(){
yield "a";
yield * newGenerator() ; // yield * 函数(),跳转到其他生成器函数的执行上
yield "b";
}
function * newGenerator(){
yield "newGenerator-1";
yield "newGenerator-2";
}
//在forof循环生成器中,是每次调用迭代next方法,
//不关心是哪一个生成器,遇到newGenerator()会重新寻址到newGenerator()方法上,
//走完了它的yield会重新回到本生成器中继续走完。
for(let value of Generator()){
console.log(value)
}
//迭代器的模样
console.log(weaObj)
wea {<closed>}
__proto__: Generator
[[GeneratorLocation]]: VM138:1
[[GeneratorStatus]]: "closed"
[[GeneratorFunction]]: ƒ * wea()
[[GeneratorReceiver]]: Window
2.生成器的使用
// 遍历dom节点
// 方法一:回调函数
function traverseDom(element,callback){
callback(element); // 用回调函数处理当前节点
element = element.firstElementChild; //对象下的第一个子元素
console.log('子:',element)
// 遍历每个子树
while(element){
// console.log("进入while")
traverseDom(element,callback) // 子元素作为副元素递归调用
element = element.nextElementSibling; // 同级的兄弟元素,可有空格
console.log('兄弟',element)
}
}
const subtree = document.getElementById('subtree');
// 通过traverseDom方法,从根节点开始遍历
// 函数调用。传了两个参数:一个形参,一个函数;
traverseDom(subtree,function(element){
console.log('父:',element);
})
// 方法二:
// 获取DOM根节点
var root = document.getElementById('subtree');
console.log(root);
// 获取根节点的孩子
function forDOM(root1) {
var children = root1.children;
// 遍历孩子
for (var i = 0; i < children.length; i++) {
var child = children[i];
console.log(child);
// 如果child下面有子节点,那么就继续遍历
child.children && forDOM(child); //等价于下面的判断
// if(child.children){
// forDOM(child);
// }
}
}
forDOM(root);
// 获取页面中的根节点--根标签
var root1 = document.getElementById('subtree');
log(root1);
//函数遍历DOM树
//根据根节点,调用fn的函数,显示的是根节点的名字
function forDom(root1) {
//调用log,显示的是节点的名字
//获取根节点中所有的子节点
var children = root1.children;
//调用遍历所有子节点的函数
forChildren(children);
}
//给我所有的子节点,我把这个子节点中的所有的子节点显示出来
function forChildren(children) {
//遍历所有的子节点
for(var i = 0;i < children.length;i++){
//每个子节点
var child = children[i];
//显示每个子节点的名字
log(child);
//判断child下面有没有子节点,如果还有子节点,那么就继续的遍历
child.children && forDom(child);
}
}
//函数调用,传入根节点
forDom(root1);
// log方法
function log(node) {
console.log("节点的名字:",node);
}
// 方法三:迭代器方式
function * DomTraversal(element) {
yield element;
element = element.firstElementChild;
while ((element)) {
//用yield*将迭代控制转移到另一个DomTraversal生成器实例上
yield* DomTraversal(element);
element = element.nextElementSibling;
}
}
const subTree = document.getElementById("subtree");
for(let element of DomTraversal(subTree)){
console.log(element);
}
3.与生成器交互,双方通信
function * Method(action){
const value = yield("hello" + "-" + action);
console.log('value',value) //"zhaosi"。
const newvalue = yield( value + "-" + action);
console.log('newvalue',newvalue)
yield( newvalue+"-"+action);
}
const ninObj = Method("anna"); // 调用生成器函数返回一个迭代对象,此时生成器函数不执行
//迭代对象暴露next方法,调用该方法,生成器函数开始执行,执行到遇到第一个yield,生成器挂起。
//此时yield如果是表达式,该表达式还没生成返回值就被挂起了。
const result1 = ninObj.next();
console.log('result1',result1);// {value: "hello-anna", done: false}
const result2 = ninObj.next('zhaosi'); //next的传参,会传给执行到上一个yield的表达式返回值
console.log('result2',result2);
const result3 = ninObj.next("change");
console.log('result3',result3);
// 第一次next,执行到yield表达式时,生成器挂起,迭代对象返回{value:"hello-anna",done:false}对象,但是此时yield表达式没生成返回值或者已经为unfined,无所谓,最终yield表达式的返回值只能在第二次next才能打印获取到;
// 第二次next(),生成器从上次挂起的地方,第一次yield表达式拿到yield返回值赋给变量value,
// 第二次next('zhaosi'),生成器从上次挂起的地方,第一次yield表达式拿到yield返回值赋给变量value,yield返回值被替换为next('zhaosi')的传参 zhaosi,所以value的值变成了zhaosi;本次执行到新的yield,迭代器返回{value:'zhaosi-anna',done:false}
// 第三次next,执行到第三个yield,如果有传参,会传入到上次到yield表达式返回值里
// 生成器抛出异常 :改善异步服务器端的通信
function * nin(){
try{
yield "hello"
console.log('hello')
}
catch(e){
if(e==='catchthis'){
console.log(e)
}
}
}
const ninObj = nin() //生成迭代器
const res1 = ninObj.next() //执行yield,返回value done的对象
console.log('res1',res1) //{value: "hello", done: false}
ninObj.throw("catchthis") //迭代器对象还有一个throw方法,抛出异常
4.探索生成器内部构成
生成器的四种状态
- 挂起开始:调用生成器,创建了生成器,不执行代码,返回迭代器,暴露next方法
- 执行:next后
- 挂起让渡:next后,执行过程中遇到了一个yield表达式,会创建包含返回值的新对象,随后挂起执行。生成器在这个状态暂停并等待继续执行
- 完成:在生成器执行期间,代码执行到return或者全部执行完。进入的状态。
生成器和函数的不同
- 标准函数仅仅会被重复调用,每次调用都会创建一个新的执行上下文
- 生成器的执行环境上下文会暂时挂起来并在将来恢复
- 控制流进入生成器时,当前创建一个新的函数上下文,并将该上下文入栈。而生成器比较特殊,不会执行任何代码,生成一个迭代器并返回。
迭代器是用来控制生成器的执行的,所以迭代器中保存着一个在他创建位置出的执行上下文。
- 当程序从生成器中执行完毕后,一般函数的执行环境上下文会从栈中弹出,并完成销毁。生成器函数会弹出,
但是因为迭代器对象还保存着引用,所以不会被销毁,类似闭包,只要函数还在,环境和变量就都存在。
从另一个角度看,生成器迭代器保持了一个对当前执行环境对引用,保证只要迭代器还需要他的时候它都在。
- `const res=nin.next()` 会重新激活对应的执行上下文。就是nin上下文,并把该上下文放入栈的顶部,从它上次离开的地方继续执行。
在产生一个值后,生成器的执行环境上下文就会从栈中弹出,
但因为有迭代器引用在所以不会销毁,生成器挂起执行,即生成器的执行环境上下文是一直保存的。