当前位置: 首页 > Web前端 > JavaScript

js异步编程面试题你能答对几道_1

时间:2023-03-26 21:43:25 JavaScript

上一节,我们学习了es6常用语法的一些知识点。在本章中,我们将学习异步编程。由于异步编程是js中最重要的内容,我们将分三章来学习异步编程涉及的重点和难点。同时,这部分也是面试经常考的范围。并发和并行面试题的区别并发和并行有什么区别?异步和本节的知识点其实不是一个概念,但这两个名词确实是很多人混淆的一个知识点。其实造成混淆的原因可能是这两个名词在中文中很相似,而在英文中却完全不同。单词。并发是一个宏观概念。我分别有任务A和任务B,我通过在一段时间内切换任务来完成这两个任务。这种情况可以变成并发。并行是一个微观的概念。假设cpu中有两个核心,那么我可以同时完成任务A和B。同时完成多个任务的情况可以称为并行。回调函数(callback)面试题:什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?回调函数应该是大家经常用到的吧。下面的代码是回调函数的一个例子:ajax(url,()=>{//processinglogic})但是回调函数有一个致命的弱点,就是很容易写出回调地狱,假设有多个请求之间的依赖关系,你可能会写如下代码:ajax(url,()=>{ajax(url,()=>{})})上面的代码看起来不利于阅读和维护,当然你可能会说解决这个问题并不容易。单独写函数并不容易。functionfirstAjax(){ajax(url1,()=>{secondAjax()})}functionsecond(){ajax(url2,()=>{})}ajax(url,()=>{firstAjax()})上面的代码看起来很好看,但是还是没有解决根本问题。回调地狱的根本问题是:嵌套函数之间存在耦合。如果函数嵌套太多,错误处理起来会很困难。当然回调函数也有其他的缺点,比如没有使用trycatch来捕捉错误,没有直接返回。发电机面试题:你对发电机了解多少?Generator是es6中比较难的概念之一。Generator最大的特点就是可以控制函数的执行。本节我们不讲什么是Generator,而是着重介绍Generator的一些容易混淆的地方。function*foo(){lety=2*(yield(x+1))letz=yield(y/3)return(x+y+z)}letit=foo(5)console.log(it.next())console.log(it.next(12))console.log(it.next(13))你可能想知道为什么值和你预期的不一样,那我一行一行的给你编码分析原因首先,Generator函数调用不同于普通函数。它将返回一个迭代器。执行第一次next时,传递的参数会被忽略,函数会暂停在yield(x+1)处,所以return5+1=6第二次执行next时,传入的参数等于return先前收益率的价值。如果不传递参数,yield将始终返回undefined。此时令y=212,所以第二个yield等于212/3=8next执行第三个时,传入的参数会传给z,所以z=13,x=5,y=24,sum等于42Generator函数一般见的不多,其实也有关系,一般配合co库使用。当然我们可以使用Generator函数来解决回调地狱的问题。我们可以将前面回调地狱的例子改写成下面的代码:)=>{})}letit=fetch()letresult1=it.next()letresult2=it.next()letresult3=it.next()Promise翻译过来就是promise的意思。这个promise在未来会有一个确切的回复,promise有三种状态,分别是:等待(pending)、完成(resolved)和拒绝(rejected)。一旦这个promise从waiting状态变成Otherstates就永远不能改变状态,也就是说一旦状态resolved,就不能再改变newPromise((resolve,reject)=>{resolve('success')//invalidreject('reject')})当我们构造Promise时,构造函数里面的代码会立即执行newPromise((resolve,reject)=>{console.log('newPromise')resolve('success')})console.log('finifsh')//newPromise->finifshPromise实现了链式调用,也就是说每次调用then之后都会返回一个Promise,而且是一个全新的Promise,因为state是不可变的。如果在then中使用return,return的值会被Promise.resolve()包装,Promise.resolve(1).then(res=>{console.log(res)//=>1return2//包装intoPromise.resolve(2)}).then(res=>{console.log(res)//=>2})当然Promise也很好的解决了回调地狱的问题,可以把之前的回调地狱示例重写如下:ajax(url).then(res=>{console.log(res)returnajax(url1)}).then(res=>{console.log(res)returnajax(url2)}).then(res=>console.log(res))都是关于Promise的一些优点和特点。事实上,它也有一些缺点。比如Promise不能取消,需要通过回调函数捕获错误。async和await面试题:async和await有什么特点,优缺点是什么?await的原理是什么?如果一个函数加了async,那么这个函数会返回一个Promiseasyncfunctiontest(){return"1"}console.log(test())//->Promise{:"1"}async是为了函数的返回值用Promise.resolve()包裹起来,和then中的返回值一样,await只能和async一起使用。asyncfunctiontest(){letvalue=awaitsleep()}async和await可以说是异步的终极方案,相比直接使用Promise,优点是处理了then的调用链,可以写代码更清晰并且准确。毕竟then写多了也恶心,也能优雅的解决回调地狱问题。当然也有一些缺点,因为await将异步代码转化成了同步代码。如果多个异步代码没有依赖关系而使用await,性能会降低。asyncfunctiontest(){//如果下面的代码没有依赖,可以使用Promise.all方法//如果有依赖,其实就是一个解决回调地狱的例子awaitfetch(url)awaitfetch(url1)awaitfetch(url2)}让我们看一个使用await的例子:leta=0letb=async()=>{a=a+await10console.log('2',a)//->'2'10}b()a++console.log('1',a)//->'1'1你可能对上面的代码有疑问,我来解释一下为什么先执行firstb,然后再执行await10变量a还是0,因为生成器是在await内部实现的,生成器会保留栈中的内容,所以此时a=0被保存,因为await是异步操作,如果后面的表达式没有返回promise,会被包装成Promise.resolve(返回值),然后执行函数外的同步代码。同步代码执行完后,再执行异步代码,使用保存的值。此时a=0+10上面解释中提到生成器是在await内部实现的。其实await是generator加Promise的语法糖,generator内部自动执行。如果你熟悉co,你其实可以自己实现这样的语法糖。定时器面试常用题:setTimeout、setInterval、requestAnimationFrame有什么特点?异步编程当然少不了定时器。常用的定时器函数有setTimeout、setInterval、requestAnimationFrame。先说最常用的setTimeout。很多人认为setTimeout就是延迟多长时间,然后应该在多长时间后执行。其实这个观点是错误的,因为js是单线程执行的。如果前面的代码影响了性能,setTimeout将不会按计划执行。当然我们可以通过代码修改setTimeout,让定时器相对准确letperiod=60*1000*60*2letstartTime=newDate().getTime()letcount=0letend=newDate().getTime()+periodletinterval=1000letcurrentInterval=intervalfunctionloop(){count++//代码执行花费的时间letoffset=newDate().getTime()-(startTime+count*interval);letdiff=end-newDate().getTime()leth=Math.floor(diff/(60*1000*60))lethdiff=diff%(60*1000*60)letm=Math.floor(hdiff/(60*1000))letmdiff=hdiff%(60*1000)lets=mdiff/(1000)letsCeil=Math.ceil(s)letsFloor=Math.floor(s)//得到消耗的时间下一个周期currentInterval=interval-offsetconsole.log('小时:'+h,'分钟:'+m,'毫秒:'+s,'秒进位:'+sCeil,'代码执行时间:'+offset,'下一个循环间隔'+currentInterval)setTimeout(loop,currentInterval)}setTimeout(loop,currentInterval)接下来我们来看setInterval。其实这个函数的作用和setTimeout基本一样,只是这个函数每隔一段时间执行一个回调函数。一般来说,不推荐使用setInterval。首先,它和setTimeout一样,不能保证任务会在预期的时间执行。二是存在执行累积问题,请看下面的伪代码functiondemo(){setInterval(function(){console.log(2)},1000)sleep(2000)}demo()在浏览器环境中,如果在定时器执行过程中发生了耗时操作,那么在耗时操作结束后会同时执行多个回调函数,这可能会造成性能问题。如果你需要一个循环定时器,你实际上可以使用requestAnimationFrame来实现functionsetInterval(callback,interval){lettimerconstnow=Date.nowletstartTime=now()letendTime=startTimeconstloop=()=>{timer=window.requestAnimationFrame(loop)endTime=now()if(endTime-startTime>=interval){startTime=endTime=now()callback(timer)}}timer=window.requestAnimationFrame(loop)返回定时器}leta=0setInterval(timer=>{console.log(1)a++if(a===3)cancelAnimationFrame(timer)},1000)首先requestAnimationFrame有自己的functionthrottling功能,基本可以保证16.6内只执行一次毫秒(在没有丢帧的情况下),而且这个函数的延时效果是准确的,不存在定时器时间不准确的问题。当然你也可以通过这个函数来实现setTimeout。