一、
function task1(){console.log('第⼀个任务')}function task2(){console.log('第⼆个任务')}function task3(){console.log('第三个任务')}function task4(){console.log('第四个任务')}task1()setTimeout(task2,1000)setTimeout(task3,500)task4()
二、
function task1(){console.log('task1执⾏')task2()console.log('task2执⾏完毕')}function task2(){console.log('task2执⾏')task3()console.log('task3执⾏完毕')}function task3(){console.log('task3执⾏')}task1()console.log('task1执⾏完毕')
三、
//以下代码会报错var i = 0;function task(){i++console.log(`递归了${i}次`)task()}task()
如何跨越递归限制
发现问题后,我们再考虑如何能通过技术⼿段跨越递归的限制。可以将代码做如下更改,这样就不会出现递归问题 了。
var i = 0;function task(){i++console.log(`递归了${i}次`)//使⽤异步任务来阻⽌递归的溢出setTimeout(function(){task()},0)}task()
这个是因为我们这⾥使⽤了异步任务去调⽤递归中的函数,那么这个函数在执⾏的时候就不只使⽤栈进⾏执⾏了。
有了异步任务之后我们的递归就不会叠加栈帧了,因为放⼊⼯作线程之后该函数就结束了,可以出栈销毁,那么在 执⾏栈中就永远都是只有⼀个任务在运⾏,这样就防⽌了栈帧的⽆限叠加,从⽽解决了⽆限递归的问题,不过异步 递归的过程是⽆法保证运⾏速度的,在实际的⼯作场景中,如果考虑性能问题,还需要使⽤while循环等解决⽅ 案,来保证运⾏效率的问题,在实际⼯作场景中,尽量避免递归循环,因为递归循环就算控制在有限栈帧的叠加, 其性能也远远不及指针循环
setTimeout(function() {console.log('timer1')}, 0)requestAnimationFrame(function(){console.log('UI update')})setTimeout(function() {console.log('timer2')}, 0)new Promise(function executor(resolve) {console.log('promise 1')resolve()console.log('promise 2')}).then(function() {console.log('promise then')})console.log('end')
解析:按照同步先⾏,异步靠后的原则,阅读代码时,先分析同步代码和异步代码,Promise对象虽然是微任务,但是 new Promise时的回调函数是同步执⾏的,所以优先输出promise 1 和 promise 2。 在resolve执⾏时Promise对象的状态变更为已完成,所以then函数的回调被注册到微任务事件中,此时并不执 ⾏,所以接下来应该输出end。 同步代码执⾏结束后,观察异步代码的宏任务和微任务,在本次的同步代码块中注册的微任务会优先执⾏,参考上 ⽂中描述的列表,Promise为微任务,setTimeout和requestAnimationFrame为宏任务,所以Promise的异步任务 会在下⼀个宏任务执⾏前执⾏,所以promise then是第四个输出的结果。 接下来参考setTimeout和requestAnimationFrame两个宏任务,这⾥的运⾏结果是多种情况。如果三个宏任务都 为setTimeout的话会按照代码编写的顺序执⾏宏任务,⽽中间包含了⼀个requestAnimationFrame,这⾥就要学 习⼀下他们的执⾏时机了。setTimeout是在程序运⾏到setTimeout时⽴即注册⼀个宏任务,所以两个setTimeout 的顺序⼀定是固定的timer1和timer2会按照顺序输出。⽽requestAnimationFrame是请求下⼀次重绘事件,所以 他的执⾏频率要参考浏览器的刷新率。
document.addEventListener('click', function(){Promise.resolve().then(()=> console.log(1));console.log(2);})document.addEventListener('click', function(){Promise.resolve().then(()=> console.log(3));console.log(4);})
这个案例代码简单易懂,但是很容易引起错误答案的出现。由于该事件是直接绑定在document上的,所以点击⽹ ⻚就会触发该事件,在代码运⾏时相当于按照顺序注册了两个点击事件,两个点击事件会被放在⼯作线程中实时监 听触发时机,当元素被点击时,两个事件会按照先后的注册顺序放⼊异步任务队列中进⾏执⾏,所以事件1和事件2 会按照代码编写的顺序触发。 这⾥就会导致有⼈分析出错误答案:2,4,1,3。 为什么不是2,4,1,3呢?由于事件执⾏时并不会阻断JS默认代码的运⾏,所以事件任务也是异步任务,并且是宏 任务,所以两个事件相当于按顺序执⾏的两个宏任务。 这样就会分出两个运⾏环境,第⼀个事件执⾏时,console.log(2);是第⼀个宏任务中的同步代码,所以他会⽴即执 ⾏,⽽Promise.resolve().then(()=> console.log(1));属于微任务,他会在下⼀个宏任务触发前执⾏,所以这⾥输出 2后会直接输出1. ⽽下⼀个事件的内容是相同道理,所以输出顺序为:2,1,4,3