zoukankan      html  css  js  c++  java
  • Generator函数的理解和使用

    原文:https://blog.csdn.net/ganyingxie123456/article/details/78152770

    Generator函数的理解和使用

    Generator 函数是 ES6 提供的一种异步编程解决方案。

    一、异步编程

    1、所谓“异步”,简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。

    2、异步编程方式:

    (1)回调函数
    
    (2)事件监听
    
    (3)发布/订阅者
    
    (4)Promise对象
    

    3、所谓回调函数,就是把第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。

    回调函数的异步方式容易形成多重嵌套,多个异步操作形成了强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改。这种情况就称为”回调函数地狱”(callback hell)。

    Promise可以解决callback hell问题,Promise对象允许回调函数的嵌套,改成链式调用。

    二、什么是Generator?

    语法上,可以把理解成,Generator 函数是一个状态机,封装了多个内部状态。

    形式上,Generator 函数是一个普通函数。

    整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方,都用yield语句。

    Generator函数特征:

    (1)function 关键字和函数之间有一个星号(*),且内部使用yield表达式,定义不同的内部状态。

    (2)调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。

    三、定义Gernerator函数

    function* fn(){   // 定义一个Generator函数
        yield 'hello';
        yield 'world';
        return 'end';
    }
    var f1 =fn();           // 调用Generator函数
    console.log(f1);        // fn {[[GeneratorStatus]]: "suspended"}
    console.log(f1.next()); // {value: "hello", done: false}
    console.log(f1.next()); // {value: "world", done: false}
    console.log(f1.next()); // {value: "end", done: true}
    console.log(f1.next()); // {value: undefined, done: true}

    但是,调用Generator函数后,函数并不执行,返回的也不是函数执行后的结果,而是一个指向内部状态的指针对象。

    下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。即:每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

    Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

    Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

    四、yield表达式和next()方法

    Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。

    yield表达式就是暂停标志。

    yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行。

    使用yield需注意:

    (1)yield语句只能用于function* 的作用域,如果function* 的内部还定义了其他的普通函数,则函数内部不允许使用yield语句。

    (2)yield语句如果参与运算,必须用括号括起来。

    return方法跟next方法的区别:

    1)return终结遍历,之后的yield语句都失效;next返回本次yield语句的返回值。
    
    2)return没有参数的时候,返回{ value: undefined, done: true };next没有参数的时候返回本次yield语句的返回值。
    
    3)return有参数的时候,覆盖本次yield语句的返回值,也就是说,返回{ value: 参数, done: true };next有参数的时候,覆盖上次yield语句的返回值,返回值可能跟参数有关(参数参与计算的话),也可能跟参数无关(参数不参与计算)。
    

    遍历器对象的next方法的运行逻辑:

    (1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
    
    (2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
    
    (3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
    
    (4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
    

    next()方法参数 
    表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

    for…of循环 
    可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。一旦next方法的返回对象的done属性为true,for…of循环就会中止,且不包含该返回对象。

    示例理解 Generator:

    // 定义一个Generator函数
    function* foo(x) {
      var y = 2 * (yield (x + 1));
      var z = yield (y / 3);
      return (x + y + z);
    }
    
    var a = foo(5);
    console.log(a.next()); // Object{value:6, done:false} 第二次运行next方法的时候不带参数,导致y的值等于2 * undefined(即NaN),除以3以后还是NaN
    console.log(a.next()); // Object{value:NaN, done:false} 第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN。
    console.log(a.next()); // Object{value:NaN, done:true}
    
    var b = foo(5);
    console.log(b.next());   // {value:6, done:false } 第一次调用b的next方法时,返回x+1的值6
    console.log(b.next(12)); // {value:8, done:false } 第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;
    console.log(b.next(13)); // {value:42, done:true } 第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。

    五、Genarator函数使用示例

    1、输出斐波那契数列

    function *fibonacci(){
        let [pre, cur] = [0,1];
        for(;;){
            [pre, cur] = [cur, pre+cur];
            yield cur;
        }
    }
    for(let n of fibonacci()){
        if( n>1000 )
            break;
        console.log(n);
    }

    2、遍历完全二叉树

    function* preOrder(root){ // 前序遍历
        if(root){
            yield  root.mid;
            yield* preOrder(root.left);
            yield* preOrder(root.right);
        }
    }
    function* inOrder(root){  // 中序遍历
        if(root){
            yield* inOrder(root.left);
            yield  root.mid;
            yield* inOrder(root.right);
        }
    }
    function* postOrder(root){ // 后序遍历
        if(root){
            yield* postOrder(root.left);
            yield* postOrder(root.right);
            yield  root.mid;
        }
    }
    function Node(left, mid, right){  // 二叉树构造函数
        this.left = left;
        this.mid = mid;
        this.right = right;
    }
    function binaryTree(arr){         // 生成二叉树
        if(arr.length == 1){
            return new Node(null, arr[0], null);
        }
        return new Node(binaryTree(arr[0]), arr[1], binaryTree(arr[2]));
    }
    
    // 完全二叉树节点
    let bTree = binaryTree([[['d'], 'b', ['e']], 'a', [['f'], 'c', ['g']]]);
    
    // 遍历结果
    var preResult = [];
    for(let node of preOrder(bTree)){  // 前序遍历结果
        preResult.push(node);
    }
    console.log(preResult);            // (7) ["a", "b", "d", "e", "c", "f", "g"]
    
    var inResult = [];
    for(let node of inOrder(bTree)){   // 中序遍历结果
        inResult.push(node);
    }
    console.log(inResult);              // (7) ["d", "b", "e", "a", "f", "c", "g"]
    
    var postResult = [];
    for(let node of postOrder(bTree)){  // 后序遍历结果
        postResult.push(node);
    }
    console.log(postResult);            // (7) ["d", "e", "b", "f", "g", "c", "a"]

    3、 Genarator 逐行读取文本文件

    function* readFileByLine(){
        let file = new FileReader("a.txt");
        try{
            while(!file.eof){
                yield parseInt(file.readLine(), 10); // 使用yield表达式可以手动逐行读取文件
            }
        }finally{
            file.close();
        }
    }
    
    var r = readFileByLine();
    r.next();

    4、Genarator 部署Ajax操作

    function* main(){ // 通过 Ajax 操作获取数据
        var result = yield request("http://some.url");
        var res = JSON.parse(result);
        console.log(res.value);
    }
    function request(url){
        makeAjaxCall(url, function(res){
            it.next(res);
        })
    }
    var it = main();
    console.log(it.next());

    5、 Genarator 对任意对象部署Iterator接口

    function* deployObjectInterface(obj){
        let keys = Object.keys(obj);
        for(let i=0; i<keys.length; i++){
            let key = keys[i];
            yield [key, obj[key]];
        }
    }
    let obj = {name:"Cynthia", age:21 };
    for(let[key, value] of deployObjectInterface(obj)){
        console.log(key, value); 
    }
    // name Cynthia
    // age 21

    6、Genarator 对数组部署Iterator接口

    function* deployArrayInterface(arr){
        var nextIndex = 0;
        while(nextIndex < arr.length){
            yield arr[nextIndex++];
        }
    }
    var arr = deployArrayInterface(['name', 'age']);
    
    console.log(arr.next());       // {value: "name", done: false}
    console.log(arr.next().value); // name
    console.log(arr.next().done);  // false
    
    console.log(arr.next().value); // age
    console.log(arr.next().done);  // true
    
    console.log(arr.next().value); // undefined
    console.log(arr.next().done);  // true
  • 相关阅读:
    字符编码与函数
    linux打印彩色字
    企业级docker仓库Harbor部署
    PyPI使用国内源
    CentOS 7.2 升级内核支持 Docker overlay 网络模式
    购物车2
    购物车
    定制 cobbler TITLE 信息
    06.密码错误3次锁定
    05.for循环语句
  • 原文地址:https://www.cnblogs.com/zyx-blog/p/9205003.html
Copyright © 2011-2022 走看看