生成器————是一种特殊的类型的函数。当从头到尾运行标准函数的时候,它最多只生成一个值。然而生成器会在几次运行请求中暂停,每次运行都可能会生成一个值。


function* weaponGenerator() { //生成器
    yield 'a1';
    yield 'a2';
    yield 'a3';
}
let weapon = weaponGenerator(); //迭代器
let item;
while (!(item = weapon.next()).done){ //迭代
    console.log(item)
}
//{ value: 'a1', done: false }
//{ value: 'a2', done: false }
//{ value: 'a3', done: false }

生成器原理:生成器能生成一组值的序列,但是每个值的生成都是基于每次请求,每次请求后,生成器要么响应一个新的值,要么告诉你没有值可以生成。每当生成器生成一个值的时候,生成器非阻塞的挂起,当另一个请求到来的时候,生成器又从上次离开的部分开始执行。

当调用一个生成器后,会创建一个迭代器(iterator)对象。通过迭代器对象可以和生成器通信,迭代器有两个方法,一个是next(),一个是throw(),当迭代器调用next之后,生成器开始工作,当生成器执行遇到yield字段的时候,就会返回一个中间值对象,该对象包括一个value(返回的值),和一个done(判断是否迭代完成)。

对迭代器进行迭代除了使用while,还可以应用for..of迭代,for...of自动调用next方法并判断done属性值:

 //...上面的生成器
for(let item of weaponGenerator()){
	console.log(item)
}
//a1
//a2
//a3

在生成器的内部可以将执行权交给下一个生成器


function* schoolGenerator(){
    yield 'headmaster';
    yield* college();
    yield 'student';
}
function* college() {
    yield 'dean';
    yield 'teacher';
}
for (let item of schoolGenerator()){
    console.log(item)
}
// headmaster, dean, teacher, student

当在schoolGenerator中执行到第二个yield字段的时候,将执行权交给college生成器,直到college被迭代完,才把执行权交给schoolGenerator。

使用生成器生成ID序列:


function* produceId(){
    let id = 0;
    while (true) {
      yield ++id;
    }
}

let ids = produceId();
let item;
while ((item = ids.next().value) <= 5){
    console.log(item)
}
//1, 2, 3, 4, 5

注意:在代码中书写了一个无限循环while代码,在普通函数中不能这样书写,但是在生成器中可以这样写。

与生成器进行交互:


function* produceText(value){
    let text = yield `hello ${ value }`;
    yield `1024 ${ value } ${ text }`;
}

let msgText = produceText('world');
console.log(msgText.next().value); //hell world
console.log(msgText.next('programmer').value); //1024 world programmer

代码实现了迭代器与生成器进行数据的交互,首先启动生成器,调用next方法后返回一个值,并且将“world”数据传回生成器存储到text中,下一次调用next的时候所以返回1024 world programmer。

注意:为啥第一次调用next的时候没有传入一个值,next为等待中的yield表达式提供值,当没有等待中的yield时,也就没有什么值可以应用到。

向生成器抛出异常:


function* NinjaGenerator(){
    try{
        yield 'Hattori';
    }catch (e) {
        console.error(e)
    }
}

let ninjaGenerator = NinjaGenerator();
console.log(ninjaGenerator.next().value);
ninjaGenerator.throw('produce Error!');
//Hattori
//produce Error!

首先使用try...catch将生成器的函数体包裹,然后调用迭代器next返回一个值,通过throw向生成器中抛出一个错误,catch捕获。

生成器的使用:(1). 向服务器请求数据

或许你曾经写过或者看过这样的代码:


try{
    var moveList = syncGetJson('moveList.json');
    var actionMove = syncGetJson(moveList[0].actionMoveUrl);
    var actionMoveDetail = syncGetJson(actionMove[0].actionMoveDetailUrl);
}catch (e) {
    //console.error('server Error!')
}

代码有一些问题,虽然catch会捕获任何一次请求错误,但是因为JavaScript是单线程,只要有一次请求耗时过长就会将UI渲染一直堵塞。或许你会写成异步回掉函数的形式:


syncGetJson('moveList.json', function (err, moveList) {
    if (err) {
        console.error('Error'+ err);
        return
    }
    syncGetJson(moveList[0].actionMoveUrl, function (err, actionMove) {
        if (err) {
            console.error('Error: '+ err);
            return
        }
        syncGetJson(actionMove[0].actionMoveDetailUrl, function (err, actionMoveDetail) {
            if (err){
                console.error('Error:' + err);
                return
            }
            //...
        })  
    })
});

代码虽然改变了用户体验,但是代码中每一个回掉都对错误处理一次,并且还有可能陷入“回掉地狱”中,很丑陋,有了生成器可以改变一切:


syncGetJson(function* () {
    try {
        let moveList = yield syncGetJson('moveList.json');
        let actionMove = yield syncGetJson(moveList[0].actionMoveUrl);
        let actionMoveDetail = yield syncGetJson(moveList[0].actionMoveDetailUrl);
    }catch (e) {
        //console.error(e)
    }
});

代码是不是比之前的更加优雅了,并且生成器不会每次请求不会阻塞UI的继续渲染,并且当任何一步请求错误都会被catch捕获。

(2). 遍历DOM树:


html:
<div id="domTree">
    <form>
        <button type="button">btn</button>
    </form>
    <p>paragraph</p>
    <span>span</span>
</div>
js:
function traverseDOM(element, callback) {
        callback(element);
        element = element.firstElementChild;
        while (element){
            traverseDOM(element, callback);
            element = element.nextElementSibling;
        }
    }

    let domTree = document.getElementById('domTree');
    traverseDOM(domTree, function (element) {
        if (element != null){
            console.log(element.nodeName.toLowerCase())
        }
    })
    //div, form, button, p, span

普通的遍历dom的样子大概就是这样,通过迭代器遍历将使代码更加的简洁:


function* traverseDOM(element) {
        yield element;
        element = element.firstElementChild;
        while (element){
            yield* traverseDOM(element);
            element = element.nextElementSibling;
        } 
    }
    
    let domTree = document.getElementById('domTree');
    for (let item of traverseDOM(domTree)){
        console.log(item.nodeName.toLowerCase())
    } 

首先执行当前的生成器,然后在while中将当前的执行权交给下一个生成器继续遍历。上述两个实例就是作者所遇到的实际中的应用,下次遇到其他的生成器的应用实例在补充,如果文章中有任何错误希望大佬进行留言纠正,下一篇继续ES6中生成器(Generator)和promise的应用,

注:本文章属于原创作品,可以转载,但是请你备注转载出处和作者。

--本文章已同步到微信公众平台,扫描下方二维码关注可快速阅读:--
打赏
X

感谢大佬的打赏😘