同步代码中dom操作不生效的问题
很多时候会遇到这种情况,由于某个执行过程很耗时间,所以想在执行前加上loading,执行后把loading去掉。
例如如下代码:
document.body.innerText = "loading"; //dom操作1
var now = new Date().valueOf()
while(new Date().valueOf() - now <= 2000) {
//do something
continue;
}
document.body.innerText = "done"; //dom操作2
看代码目的执行效果应该是页面上显示loading,2s后变成done,但大家可以试一下,执行出来结果应该是页面2s之后直接变成done,并没有出来loading。
之所以出现这样的现象是因为浏览器内核是多线程的,代码执行和dom渲染分别由javascript引擎线程和GUI渲染线程负责,且js线程与gui渲染线程是互斥的,当js线程执行时,渲染线程呈挂起状态,只有当js线程空闲时渲染线程才会执行。
而javascript是单线程执行的,只有当调用栈中的所有同步任务执行完毕,线程才会空闲出来,去调用GUI渲染,执行dom的操作。
基于以上的原理,再看上面的代码,js会在所有的同步代码执行完,才会渲染dom,所以用户看到的就是js执行了2s,页面直接出现done
如果真的有业务要实现这样的效果怎么办呢?
1、使用异步操作(或setTimeout等),把要执行的dom操作脱离当前调用栈,放到任务队列里。这样就会在调用栈中的代码执行完毕,线程空闲出来,执行一次dom操作后,在执行任务队列中的任务,从而执行第二次dom操作。如下代码
document.body.innerText = "loading"; //dom操作1
setTimeout(function(){
//do something
document.body.innerText = "done"; //dom操作2
},2000)
2、网上还有另一种方法,使用alert语句会阻塞js线程,故将执行权让给gui渲染线程,所以之前的dom的操作都进行体现。如下代码
document.body.innerText = "loading"; //dom操作1
alert("doing")
document.body.innerText = "done"; //dom操作2
但亲测这种方法只在火狐中好使,在chrome和Edge中都没有出来loading。而且直接弹框alert用户体验也不太好,所以这种方法实际意义和效果都不推荐。
看到这儿了再深入一下javascript的运行机制
我们都知道任务执行有同步执行和异步执行。实际上正常的同步任务都会在主线程上执行,形成一个“执行栈”。而除了这个“执行栈”,还会在主线程外有一个“任务队列”。只要异步任务有了运行结果,就会在任务队列中放置一个事件,当“执行栈”所有的同步任务完成之后,系统会读取“任务队列”,放入“执行栈”,开始执行。