springboot实战电商项目mall4j (https://gitee.com/gz-yami/mall4j)
1.介绍
工作一段时间了,今天在这总结一下浏览器执行JS任务机制。在本人日常工作中,做的最多的两点,分别是
1.对设计图进行HTML CSS的编写
2.对接口请求来的数据进行页面渲染
对请求回来的数据进行处理时会涉及到异步函数,而JS执行代码的时候会先执行同步任务,再执行异步任务。
这时要是执行同步任务里面需要用到一个异步任务的里面值就会抛出错误,为什么会这样了,下面就从基本的
开始讲解了。
2.浏览器内核
浏览器内核是多线程的,具体有那些呢如下图所示
## 2.1 GUI渲染线程
主要是负责渲染和解析HTML,CSS,构建DOM树,当对HTML标签进行v-if v-show 等诸如此类的操作时会触发界面的重绘和重排
此时该线程就会执行。
在JS引擎执行时会处于休眠状态 会放到另一个任务队列中,当JS对DOM,CSS等进行操作时,会保留更新状态等到JS引擎空闲时
就会被立即执行。
2.2 JS引擎线程
主要负责处理JavaScript脚本程序,JS引擎在处理完主线程的任务时,会一直等待任务队列的任务到来,直到无任务可执行,此时就会
涉及到一个问题,因为JS是可以对Dom进行操作的,这样就有可能会出现渲染出错的一系列问题,所以浏览器是不允许两者同时执行的
GUI线程会等到JS线程执行完后才会执行期间做出的GUI更新会保存起来。
2.3 异步http请求线程
在进行网络请求时,会开辟一个新进程,防止阻塞JS引擎执行,当状态发生变化时会从任务队列中调回JS主线程中,如请求结果是400,200 这些状态时
2.4定时器触发线程
JS代码中setTimeout和setInterval,这两个定时器触发时为了不影响其准确性,浏览器也会新开个线程,来放置,当计时结束时会先放到
事件线程中 然后在等JS引擎空闲时执行
2.5事件触发线程
JS中的点击 触摸等事件触发该线程时 会把事件添加到待处理队列的队尾,等待JS引擎的处理
3.宏任务和微任务
相对于浏览器的多线程,JS引擎则是单线程的,其中的历史原因就不细讲了。正因是单线程,JS执行代码时从上而下执行时,会有HTTP请求,定时器等,不可能等到他们执行完才进行下一步,这样会影响用户体验,这时就需要任务队列的帮助了,下面画了张图便于理解
当主线程的任务执行完后,会向任务队列查询有无可执行的任务,有就拉到主进程执行,没有就不停进行轮询,直到主进程和任务队列都没有有任务了就会停下来。
上图我画了两个任务队列是因为在JS中有分宏任务和微任务两种,宏任务执行完后会执行微任务 微任务执行完后会执行宏任务,而主线程算作一个宏任务,所以当主线程的任务执行完后,就会从微任务里查询有无任务可执行,没有就前往宏任务寻找。
宏任务最常见的两个是setTimeout和setInterval,(主线程也是宏任务),而微任最常见的则是Promise.then()里面的内容。
setTimeout(()=>{
console.log('宏任务')
},500)
Promise.resolve().then(value=>{
console.log('微任务')
})
console.log('已在主进程中,准备执行的任务')
JS任务执行是主进程任务执行完了再执行微任务再宏任务,所以不管你宏任务的定时器设置的有多快都得等到主任务的和微任务执行完才能轮到你执行。
setTimeout(()=>{
console.log('等到花都谢了')
},5)
Promise.resolve().then(value=>{
for (var i = 0; i<999999999; i++) {
console.log('等我跑完')
}
})
console.log('已走完')
当然定时器也不会傻傻的等到同步任务执行完后才开始计时,上面说到的定时器触发线程就是为此准备的,当代码执行到定时器的时候会把它扔到定时器线程进行计时,当计时完毕后就会按快慢来依次放到宏任务队列里边,相同时间则会按先后顺序执行。
setTimeout(()=>{
console.log('明明是我先进去的')
},1000)
setTimeout(()=>{
console.log('谁叫我速度比你快')
},500)
console.log('坐等')
面试题很喜欢考的一个就是promise里嵌套setTimeout,setTimeout里嵌套promise只要记住一点就不用怕就是
当主线程里没有任务执行了就会先去微任务队列里把任务一个个找出来执行等执行完了所有微任务,才会去宏任务里面找任务执行,就算在微任务里面发现定时器这些宏任务,也会把他们先放到别的任务队列里等这边队列执行完后再去执行。
在主线程中声明的全局变量,在宏任务或者微任务调用的时候是共用的就算定时器时间相等也会按顺序打出
var a = 0
for (var i = 0; i<100; i++) {
setTimeout(()=>{
console.log(++a)
},1000)
}
当然要是只用考虑这些任务队列就简单了,但我们对这些数据进行处理后需要把他们渲染到页面的时候就又有另一种情况了
由浏览器来决定渲染刷新的频率,这个频率通常跟用户的显示屏率有关,60hz的话即每秒刷新60次,因为每秒渲染是次数固定的,但一旦一个任务执行了成千上万次的CSS的改变就会影响下一次的渲染,进程上的任务会按顺序来执行,但抛到每帧渲染的时候就不会按顺序来了,像模块移动的越来越快,屏幕闪屏。所以再编写代码时可以调用requestAnimationFrame这个回调函数来限制,
function callback() {
moveBox() //改变css样式的函数
reuquestAnimationFrame(callback)
} //稳步前进
function callback() {
moveBox() //改变css样式的函数
setTimeout(callback,100)
} // 越来越快 闪屏
js执行任务时要是遇到代码错误就会阻塞运行,卡在一处,不管浏览器怎么催促涉及到页面渲染的操作都执行不了,也就出现了页面卡死点击没反应的情况
4.Promise
promise对于解决回调地狱和简洁我们的代码具有很大的作用,promise有三种状态,pending等待,另外两个是resolve决定和reject拒绝,初始new一个promise实例时要是不发送通知的话后面的.then()里面的代码是不会执行的
new Promise((resolve,reject)=>{
console.log()
//必须得有resolve()或reject()其中之一
}).then(value=>{
console.log("坐等")
})
//满足条件后.then()里的执行代码就会被送去微任务队列了,此时再也无法改变promis的状态了