从零开始学_JavaScript_系列(54)——Generator函数(2)简单应用、throw和return
来源:互联网 发布:惠州网络推广公司 编辑:程序博客网 时间:2024/05/21 08:38
2、基本应用
2.1、解构赋值
数组是可以通过Iterator接口进行解构赋值的,对象不行是因为对象没有这个接口,那么假如我们给对象补上这个接口,对象自然也可以了。
而Generator函数显然是Iterator的完美搭配(因为他能返回一个遍历器),将Generator函数作为对象的[Symbol.iterator]
属性即可。
如代码:
let foo = { a: '1', b: '2', * [Symbol.iterator]() { for (let i in this) { yield this[i] } }}let bar = [...foo]bar; // ['1', '2']
2.2、可以通过for…of来遍历
Generator函数返回的是一个遍历器,而for…of可以遍历遍历器,所以显然for…of也可以遍历Generator函数的返回值(注意:是返回值)
如代码:
function* foo() { yield 1; yield 2; return 3}for (let i of foo()) { console.log(i)}// 1// 2
但注意:通过 return
返回的值,并不会被遍历到(如上面的结果是没有3的)。
另外,解构赋值等,也会忽视return返回的值。
3、throw和return
3.1、throw
gen.throw(exception)
throw方法在Generator的原型链上。
throw和next相对来说,比较特殊,他具备以下特点:
- 和next一样,是通过迭代器来执行的;
- 和next一样,从上一次yield表达式所在的地方开始执行代码;
- 但区别在于,next会继续执行下一句代码,而throw相当于在开始执行下一句代码之前,立刻先执行了一个
throw err
,err就是throw的参数(建议err使用new Error(err)
的形式); - 当抛错时,根据其上一次yield表达式是否在
try...catch
捕获的作用于内,分为两种情况:
4.1 假如不在其中,无法捕获到报错,那么throw语句没有返回值,代码停止继续运行。下次执行next()时,返回值变为{value: undefined, done: true}
4.2 假如在其中,可以捕获到报错,那么立刻进入catch
代码块,然后继续正常执行到下一个yield表达式的位置,并且该yield表达式的值将成为throw语句的返回值(就像执行了next()
语句一样) - 由于4.2的情况很正常,所以不做讨论。假如遇见4.1的情况,那么错误还将被抛到Generator函数外,遍历器执行throw()的地方,就像在遍历器执行throw的地方执行了一次
throw err
一样。假如此时该错误没有被try...catch
所捕获,那么将解释器按照抛错处理(比如说继续停止之后的代码)
简单来说,相当于在执行了一次next(),并且在Generator函数内开始执行应执行的代码之前,抛错。如果错误没有被捕获,内部执行抛错处理,并且该错误会冒泡到执行throw()的地方,并且按照正常出错的情况来处理。
请结合代码理解:
function *g() { try { yield; // 由于抛错,下面这个console.log不会被执行 console.log('上一个yield表达式之后,同在try里的代码块。因为throw所以不会执行这里') } catch (e) { // 因为throw,所以错误被捕获到了 console.log('内部捕获', e); } console.log('内部在catch捕获后的代码块,这里会在catch后继续执行') yield '第二个yield,成为foo的值' console.log("第二个yield之后,也会继续执行") yield '第三个yield,成为bar的值'};var i = g();// 过渡到第一个yield表达式的位置(此时在try...catch语句内)i.next();let foo;let foo2;try { foo = i.throw('a'); // foo2 = i.throw('a'); // 如果解除注释,这个抛错将被"外部捕获"捕捉到 // foo2的值是undefined} catch (e) { console.log('外部捕获', e); // 如果没有这个try...catch捕捉错误,那么代码将报错(停止继续执行)}// 内部捕获 a// 外部捕获 blet bar = i.next()console.log(foo) // {value: "第二个yield,成为foo的值", done: false}console.log(bar) // {value: "第三个yield,成为bar的值", done: false}
另外,i.throw()和throw的区别在于,后者只会影响到外部,而前者先影响到Generator内部,未处理的话才会冒泡到外部。
简单总结一下:
- 遍历器的throw方法可以将错误传入Generator函数内部;
- Generator函数内部可以通过try…catch来捕获错误,如果没有被捕获,将冒泡到Generator函数外面来;
- 如果在Generator函数内部正常捕获了错误,那么代码将继续执行下去;
- 如果没有捕获到错误,遍历器的done将变为true;
- 可以用于调试Generator函数内部错误捕获;
3.2、return
gen.return(value)
返回值是{value: value, done: true}
,value是传入的值。
同样是在Generator的原型链上。
简单来说,相当于立即结束了Generator函数(除了当时yield在try...finally
代码块以内的情况)。
如代码:
let g = function*() { console.log("Before 1") yield 1; console.log("Before 2") yield 2; console.log("Before 3") yield 3;};let foo = g();foo.next();// Before 1、// {value: 1, done: false}foo.return('input');// {value: "input", done: true}foo.next();// {value: undefined, done: true}
- 在第一次执行完next后,停留在
yield 1
; - 然而当执行return后,并没有继续执行到
yield 2
,而是立刻跳到函数结束,并且返回的对象的done为true; - 可说明Generator函数已经遍历完毕了(虽然是被终止的);
- 从之后调用foo.next()的返回值也可以得到证明;
- 需要特别注意的是:return并非是跳转到Generator函数的结尾来实现done变为true的,而是直接停止了Generator函数。这点可以通过下面的
try...finally
来证明。
finally和return
当return遇见try...finally
时,如果当前的yield在try的代码块内,那么将直接跳到finally代码块内,执行finally代码。
等finally里的代码执行完毕后,才会立刻结束Generator函数,并将return的参数作为return的返回值对象的value属性的值。
有一点比较特殊:
- 假如finally里有yield表达式,那么return并不会造成Generator函数的结束,而是在遇见yield表达式后停止(就像正常那样);
- 可以通过继续调用next往下执行,直到离开finally代码块为止;
- 离开finally代码块后不会继续执行,而是立即结束Generator函数
let g = function*() { console.log("Before 1") try { yield 1; console.log("Before 2") yield 2; } finally { console.log('finally') yield 4; } console.log("last") yield 5;};let foo = g();foo.next();// Before 1// {value: 1, done: false}foo.return('input');// finally// {value: 4, done: false}foo.next();// {value: "input", done: true}
简单来说,return用于终结Generator函数。
- 从零开始学_JavaScript_系列(54)——Generator函数(2)简单应用、throw和return
- 从零开始学_JavaScript_系列(57)——Generator函数(5)状态机与函数的应用
- 从零开始学_JavaScript_系列(53)——Generator函数(1)基本概念和示例
- 从零开始学_JavaScript_系列(62)——class(3)setter和getter、Generator、async函数
- 从零开始学_JavaScript_系列(55)——Generator函数(3)yield*表达式
- 从零开始学_JavaScript_系列(56)——Generator函数(4)简写,this与继承
- 从零开始学_JavaScript_系列(58)——Thunk函数
- 从零开始学_JavaScript_系列(59)——async函数
- 从零开始学_JavaScript_系列(64)——class的继承(1)基本概念、继承构造函数和class
- 从零开始学_JavaScript_系列(30)——NodeList
- 从零开始学_JavaScript_系列(32)——事件广播
- 从零开始学_JavaScript_系列(43)——Symbol简述
- 从零开始学_JavaScript_系列(47)——Reflect
- 从零开始学_JavaScript_系列(29)——apply和call
- 从零开始学_JavaScript_系列(44)——ES6新增数据结构:Set类型和WeakSet
- 从零开始学_JavaScript_系列(45)——ES6新增数据结构:Map和WeakMap
- 从零开始学_JavaScript_系列(51)——Promise(4)Promise.resolve和Promise.reject
- 从零开始学_JavaScript_系列(63)——class(4)静态方法和new.target
- JAVA 动态代理
- 枚举单例正确的打开方式,之前理解的都是错的,一把辛酸泪啊
- 这是我的第一篇文章
- c#的预编译指令
- eclipse快捷键使用
- 从零开始学_JavaScript_系列(54)——Generator函数(2)简单应用、throw和return
- 8-21 DAIRY
- java面试题和答案(2)
- DeepLearning(花书)第二章主要内容(7-12节)
- vs qt 环境配置
- 56. Merge Intervals
- 图像分割结果的评估
- hdu 6138 Fleet of the Eternal Throne(AC自动机)
- 关于zookeeper第三方客户端zkclient的使用说明