1、简述Generator函数
答:① Generator函数是一种异步编程方案
② 从语法上,Generator是一个状态机,里面封存了多个内部状态
③ Generator函数是一个遍历器生成函数
④ 语法是, function* 函数名,函数内部使用yield表达式(yield只能用在Generator中,用在其他函数会报错),用来定义不同的内部状态
⑤ Generator函数调用后,并不执行和返回运行结果,而是一个指向内部状态的指针对象
⑥ 可以使用next()方法来依次访问Generator的状态
function* fun(){
yield '1'
yield '2'
return '333' // 结束执行
}
var f = fun()
fun().next() // {value: '1', done: false}
f.next() // {value: '1', done: false}
for (let val of f){console.log(val)}
for (let val of fun()){console.log(val)}
/*上面两个循环结果 输出:
'1'
'2'
*/
2、Generator函数的星号(*)写在哪里呢?
答:写在function关键字和函数名之间。下面写法都行
function * foo(){}
function* foo(){}
funtion foo(){}
functionfoo(){}
3、Generator函数和普通函数的区别
答:① 定义方式不一样,普通函数定义格式是 ,函数名(){},Generator函数定义格式是,函数名*(){}
② 普通函数和Generator函数都可以使用return来结束代码的执行,且只能有一个return来结束
③ Generator函数内部必须使用yield来定义不同的状态,普通函数只是逻辑代码的执行
④ Generator函数调用并不是真正的调用执行,返回也不是真的运行结果,二是一个直指向内部状态的指针对象
⑤ Generator函数可用于遍历器对象的生成,遍历器内部具有next方法。
⑥ Generator函数是分段执行的,遇到一个yield就停止,next方法返回一个对象,当对象中的done:false时,继续执行直到遇到return或者done:true停止
4、简述Generator函数中的yield表达式
答:① 它是Generator函数的暂停标志,当执行过程中遇到yield,就暂停它后面的代码,
并将yield后面的值作为对象value的值,并继续执行next方法,反复进行操作。
② 当遇到return语句,就把return后面的表达式值作为value的值
③ 如果函数执行最后没有return语句,就将undefined作为value的值
④ yield只能用在Generator函数中,用在其他函数中会报错
⑤ yield表达式如果用在另外的表达式中,必须使用圆括号括起来
⑥ yield表达式用作函数的参数或者放在赋值表达式的右边可以不加括号
function* fun(){
yield 12+13 // 不会立即求值,只会在next方法将指针移到这句的时候才会求值,惰性的。
'Hello' + (yeild 123) // 在另外的表达式中,yield需要用括号
let input = yeild // 赋值表达式等号右边,可不用括号
foo(yeild 'a', yeild 'b') // 作为函数的入参,可不用括号
}
5、当Generator函数中没有yield时,怎么处理
答:此时Generator函数变成了一个暂缓执行的函数,只有当调用next方法的时候,函数才会执行
function * fun(){
console.log(1111)
}
var g = fun()
setTimeout( () => { g.next() }, 3000)// 3秒输出 1111
6、使用Generator函数,让对象具有iterator接口
答:直接将Generator赋值给对象的 Symbol.iterator属性
var obj = {}
obj[Symbol.iterator] = function*(){
yield 1;
yield 2;
}
[...obj] // [1, 2]
7、Generator函数中next方法
答:如果yield没有返回值挥着总返回undefined,可以使用next方法的入参作为上一个yield的返回值
function* fun(x){
var a = yield x;
var b = yield (a * 2);
return (b + 3)
}
var g = fun()
g.next() // {value: undefined, done: false}
g.next(12) // {value: 24, done: false} ----上一个yield返回值为 12,即a为12;那么当前yield返回值为 12*2
g.next(24) // {value: 27, done: true}----上一个yield返回值为 24,即b为24;那么当前yield返回值为 24+3
8、使用for…of遍历Generator函数运行时生成的iterator对象
答:for…of会依次输出 yield返回值,即done:true的对象的value;return语句返回值不包括在内。
function* fun(){
yield 1;
yield 2;
return 6
}
for(let val of fun()){console.log(val)} // 依次输出 1和2
9、Generator函数的return
答:① return主要用来返回一个值并中止程序的运行,可用在Generator函数内部语句和外部方法。
② 如果return不提供返回值,那么返回值为undefined
③ 如果Generator函数有try…finally时,执行外部执行return方法,会导致直接进入finally代码块,finally代码块执行完,再返回return方法中指定的返回值。
function * fun(){
yield 1
yield 2
yiled 3
}
var g = fun()
g.next() // {value: 1, done: false}
g.return(1111) // {value: 111, done: true}
g.next() // {value: undefined, done: true}
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
10、简述Generator函数中的next() 、throw() 和return()方法共同点
答:它们的作用都是让Generator函数恢复执行,并且会使用不同的语句替代yield表达式。
next()将yield表达式替换成一个值
throw()将yield表达式替换成一个throw语句
return()将yield表达式替换成一个return语句
function* fun(){
yield 1;
}
var g = fun()
g.next() // Object {value: 1, done: false}
g.next(222) // Object {value: 222, done: false}
// 相当于将 let result = yield 1
// 替换成 let result = 222
g.throw(new Error('出错了')) // Uncaught Error: 出错了
// 相当于 let res = yield 1
// 替换成 let res = throw(new Error('出错了'));
g.return(2); // Object {value: 2, done: true}
// 相当于将 let res = yield x + y
// 替换成 let res = return 2;
11、简述 yield*
答:① 如果Generator函数内部想要调用另一个Generator函数,可以使用 yield或者for…of循环,
返回一个遍历器对象内部值。
② yieldGenerator函数名 相当于用 for…of遍历这个Generator函数。
③ yield* 后面可以跟着一个具有iterator接口的遍历器,会依次遍历里面的成员
④ 如果yield*后面的Generator函数里有return值,这个return值可以用一个变量进行承接
function * fun1(){
yield 1;
yield 2;
}
function * fun2(){
yield 4;
yield 5;
return 333
}
function * fun3(){
yield 111
yield* fun1() // 这个相当于 for( let val of fun1() ){ yield val; }
let res = yield* fun1() // fun1中的return值赋给res,即res等于 333
yield 222
}
var g = fun3()
[...g] // [111, 1, 2, 4, 5,222]
12、Generator函数作为对象的属性应该怎么写
答,有两种写法
var obj = {
* fun(){.....}
}
或者
var obj = {
fun: function*(){....}
}
13、Generator函数的prototype方法可以被它的实例继承吗?
答:可以
function * fun(){
yield 1
}
fun.prototype.run = function(){console.log('run')}
var g = fun()
g.run() // 'run'
g instanceof fun // true
14、Generator函数内的this是它的实例吗?如果不是,怎么让this指向实例?
答:不是
function *fun(){
this.a = 1
}
var g = fun()
g.a // undefined
// 实现 Generator内部的this指向其实例
function *F(){
yield 2;
this.a = 111
}
var g = {}
var f = F.call(F.propetype)
f.next() // {value: 2, done: false}
f.a // 111
15、Generator函数可以配合new使用吗?如果不行,怎么可以呢?
答:不行,会报错。
function * fun(){
yield 111
}
var g = new fun() // 报错
// 让Generator可以配合new进行使用
function * fun(){
yield 2
this.a = 111
console.log('F')
}
function F(){
return fun.call(fun.prototype)
}
var f = new F()
f.next() // {value: 2, done: false}
f.a // 111
16、Generator函数的应用
答:① 异步操作的同步化,例如可以将异步的ajax请求进行同步化
② 控制流管理,例如让多个嵌套的回调函数,按次序依次执行
③ 部署任意对象的iterator接口
④ 看作是数据结构,例如看作是一个数组结构
17、异步编程的方法有哪些
答:回调函数 、事件监听、发布/订阅 、Promise对象、Generator函数
18、什么叫做异步?
答:简单的说一个任务不是连续完成的,需要分成两段,先执行第一段,
然后转而执行其他任务,等做好准备后再回过头来做第二段。
举个例子:一个文件读取的任务,任务的第一段是向操作系统发出请求,
要求读取文件。然后程序执行其他任务,等操作系统返回文件后再接着执行任务的第二段(处理文件)
19、什么叫做同步?
答:就是一个任务是连续执行的,中间不能穿插其他任务,其他任务只能等待该任务完成后再执行。
365、简述对回调函数的理解
答:回调函数是早期异步编程的重要手段。一般异步编程任务切割为两段执行,而回调函数就是最后一段。
回调函数的第一个参数必须是错误对象或null,这是因为第一段执行完后,执行的上下文环境就没办法捕捉,
只能通过作为回调函数的参数传入第二段。
fs.readFile(‘/index.js’, ‘utf-8’, function(err, data){…})
20、简述Promise作为异步编程的个人理解
答:异步编程中的回调函数缺点就是如果有多个回调函数,很难管理,如果有一个操作需要修改,
那么上下回调函数可能都需要修改,陷入‘回调地狱’。
Promise的链式调用,很好的解决这种函数不断嵌套带来的问题。
Promise最大的问题就是什么操作都是一大堆的then,没有什么语义进行更好的解释区分
var fs = require(‘fs-readFile-promise’)
fs(file).then(data => {}).then(data => {}).then(data => {}).catch(err => {})
21、简述异步编程方案中Generator函数的 ‘协程’
答:协程是异步编程的一种解决方案,就是多个线程相互协作,完成异步任务。
协程遇到yield命令就暂停执行,等到执行权回归,再从暂停的地方继续往后执行。
协程有点像函数,有点像线程,执行过程一般如下:
第一步:协程A开始执行
第二步:协程A执行到一半,暂停,执行权转移到协程B
第三步:一段时间后协程B交还执行权给A,A恢复执行
function* gen(x) {
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
22、为什么Generator函数可以封装异步任务
答:① 根本原因是它可以暂停执行和恢复执行
② 它可以通过 next() 、throw() 、return()方法实现函数体外的数据交换
④ 它有自己的错误处理机制,且不影响全局的错误处理机制
var fetch = require(‘node-fetch’);
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
23、什么是‘求值策略’及具体有些?
答:就是函数的参数到底应该什么时候求值。
‘求值策略’有两种。一种是 ‘传值调用’,即进入函数体内之前就计算;
另一种是‘传名调用’,即直接将表达式传入函数体,等用到它的时候再求值
var x = 1
function f(m){return m*2}
f(x+5) // 如果使用‘传值调用’那么等同于 f(6);’传名调用‘等同于 (x+5)*2
24、Generator函数中的Thunk函数
答:是自动执行 Generator函数的一种方法
采用‘求值策略’中的‘传值调用’
function f(m) {
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk) {
return thunk() * 2;
}