1、介绍
JavaScript是一种单线程(从上到下、从左到右执行)语言,一个时间节点只能做一件事。
单线程缺点:比如发送http请求时,等待数据返回之前网络便会出现假死的情况,因为javascript在同一个时间只能做一件事,这就导致了页面渲染和数据的执行在这个过程无法执行。但实际开发种我们并没有见过。
2、同步异步
同步【阻塞】和异步【非阻塞】的执行模式 的出现 很好的解决了单线程的诟病。
同步:严格按照单线程模式从上到下执行,执行过程中遇到问题(例如死循环)便不会执行下边的代码,造成阻塞
异步:js从上到下执行,遇到异步代码会将其挂起,等到所有的同步代码执行完毕,再执行挂起的异步代码
3、递归
函数内嵌套函数,海量函数时会出现栈溢出存在风险问题
如何防止出现栈溢出现象呢? 可以将内部调用的函数采用异步调用的方式。
//无限递归,发生栈溢出(栈的深度根据浏览器引擎和js引擎有区别)
var i = 0
function task(){
i++
console.log(`递归了${i}次`)
task()
}
task()
//通过异步调用改写后
var i = 0
function task(){
i++
setTimeout(()=>{
console.log(`递归了${i}次`)
task()
},0)
}
task()
4、宏任务和微任务
js中存在两种异步任务:宏任务与微任务
宏任务: 包括setTimeout
、setInterval
、AJAX
等,
微任务:随着ECMA升级提出的新的异步任务,例如 Promise.then
、Promise.then
。每一个宏任务执行前,会检测当前事件循环中是否含有未执行的微任务,如果有则首先执行微任务,执行完本次微任务后再执行下一个宏任务。每个宏任务内部都可注册当次任务的微任务队列,在下一个宏任务执行前运行。
5、经典笔试题
setTimeout(function() {console.log('timer1')}, 0) //宏任务1
requestAnimationFrame(function(){ //宏任务
console.log('UI update')
})
setTimeout(function() {console.log('timer2')}, 0) //宏任务2
new Promise(function executor(resolve) {
console.log('promise 1') //同步1
resolve()
console.log('promise 2') //同步2
}).then(function() {
console.log('promise then') //微任务1
})
console.log('end') //同步3
//结果1: promise 1 、promise 2 、end 、promise then 、timer1、timer2 、UI update
//结果2: promise 1 、promise 2 、end 、promise then 、UI update 、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); //同步
})
//结果 2、1、4、3
原因:事件任务也是异步任务,并且是宏任务。微任务会在下一个宏任务触发之前执行。