ES6之Generator函数迭代运用

225 阅读4分钟

今天我们在来讨论ES6中的Generator函数用法,Ganerator对于目前的定位来说,有很多的理解角度,可以当做迭代的扩展,也可以当做异步解决方案,也可以当做状态机,具体看如何使用Generator函数,今天我将分享Generator的迭代实现。

Generator函数是一种普通函数方式申明 ,中间以*号来代表,*号确没有明确规定写在function与函数名某个位置,只需要写在两者之间即可,比如:

//以下都是被允许的
function* call(){}
function * call(){}
function *call(){}

在使用Generator函数之前,我们先来了解一下yield关键字,yield只能出现在Generator函数中,不允使用在Generator函数之外,yield后面所输出的值被称为状态属性,同时它会暂停当前脚本,直接下次被next调用,需要注意的是,yield后面的状态值如果使用到了复杂的运算或者语句,都不会被立即持行,只会在next被调用时才会持行。

function* call(){
        yield "等待消息";
}
let phone = call();
phone.next();  // { value : "等待消息", done : false}

function* call(){
        yield "等待消息" + "?";
}
let phone = call();    //求值不会立即被持行
phone.next(); // { value : "等待消息?", done : false}

(function call(){
        yield "等待消息";
})();     //SyntaxError : yield is a reserved word

(function* call(a){
    (function(){ yield a; })();
})();     //SyntaxError : yield is a reserved word

在Generator函数中是可以使用return关键字的,如果使用return 关键字,其返回值只能在next之后接收,如果没有使用return关键字,多次使用next会返回一个undefined值。

function* call(){
        yield "等待消息";
        return "bye";
}
let phone = call();
phone.next();   // {value : "等待消息", done : false }
phone.next();  // {value : "bye", done : true}
function* call(){
        yield "等待消息";
}
let phone = call();
phone.next();   //{value: "等待消息", done : false}
phone.next();   //{value: "undefined", done: true}

看了上述三个例子,我们大概知道了Generator函数的使用技巧及它的作用,现在我们来看看Generator函数的next带参运用。

Generator函数的next接口是允许带参的,值得注意的是,next 参数值是给yield进行赋值, yield不会被状态值给覆盖,也就是说,next参数不是上一次yield的返回值。

function* call(name){
      let temp = yield `打电话给${name}!`;
      return temp;
}
let phone = call("Song");
phone.next(); // { value : "打电话给Song!", done : false }
phone.next(); // { value : undefined, done : true }

function* call(name){
     let isAgree = yield `打电话给${name}!`;
     if(isAgree)
          yield "Hello!"
     else
          yield "bye!"
};
let phone = call("Song");
phone.next();    // { value : "打电话给Song!", done : false}
phone.next(true);        //{ value : "Hello", done : false }

在上述例子中,调用call时,将参数"Song"传递给Generator内部,在第一次持行next的时候进行运算,输出第一句结果,第二次持行next时,会将next中的参数当做yield返回值给变量,所以在第一个call和第二个call中,给出的结果是不一致的。

接下来,我们再来了解一下Generator函数和迭代之间的关系,在JavaScript中,我们知道只有for...in 和 for循环,但现在ES6有扩展for...of循环,它与 for...in之间的区别在于一个循环下标,一个循环其结果内容,比如:

let temp = [1, 3, 5, 7, 9];
//0, 1, 2, 3, 4
for(let item in temp)
    console.log(item);
//1, 3, 5, 7, 8
for(let item of temp)
    console.log(item);

for...of 也可以对Generator函数进行操作。

function* list(){
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    return 5;
}
// 1, 2, 3, 4
for(let item of list())
    console.log(item);

我们可以看到,使用for...of 对Generator函数遍历时是不需要使用next接口的,需要注意的是,如果当遍历到done值如果是false,就会终止for循环,return值是不被会获取到的,在我们前面第四章讲到必须实现了iterator接口对象才可以调用扩展运算符,虽然数组和Object都有iterator接口实现,但是只有数组才可以使用扩展运算符,唯一的缺陷是Object不可以使用,今天我们可以利用Generator函数来完成这一工作。

function* list(){
    yield 1;
    yield 2;
    yield 3;
    yield 4;
}
let result = [...list()];    //[1, 2, 3, 4]

console.log(result); //[1, 2, 3, 4] 我们看到list函数可以被扩展运算符使用,利用这点,我们可以完成Object扩展运算符实现。

Object.prototype.toArray = function*(){
     for(let key of Object.keys(this))
        if(this.hasOwnProperty(key))
            yield { key, value : this[key] };
}
let user = [...{ id : 1, name :"Song", role : "developer" }.toArray()];
//[{ key : 'id', value : 1}, 
// { key : 'name', value : 'Song'},
// { key : 'role', value : 'developer'}]
console.log(user);

我们对全局对象Object进行扩展出toArray接口,实现对象的扩展运算符,但这种方式看起必不直观,达不到扩展运算符的真正意义,那我们来更进一步的实现Object扩展运算,如下:

Object.prototype[Symbol.iterator] = function* (){
     let keys = Object.keys(this);
     for(let item of keys)
        yield { [item] : this[item] };
}
let user = { id : 1, name :"Song", role : "developer"};
console.log([...user]); // [{ id : 1}, { name : "Song"}, { role : "developer"}]

我们对对象Object的原子链进行扩展迭代接口,使所有对象都支持迭代,然后对任意一个对象进行使用(...)扩展运算符,发现我们可以对对象做到分解,这就是Generator函数带来的好处,当然,它的用处不止这一点,我们将在下一章中继续讨论Generator函数的其它应用。