这几天看到一个有趣的问题,是关于setTimeout的,题目如下:
问:是页面是先渲染出1000个元素,还是控制台先打印出数据?如果渲染1000000个元素,谁先进行?
<div id="app"></div>
<script>
window.onload = function () {
let startTime = +new Date();
let oApp = document.getElementById('app');
for(let i=0; i<1000; i++) {
let oP = document.createElement('p');
oP.innerHTML = i;
oApp.appendChild(oP);
}
let endTime = +new Date();
console.log('输出时间', startTime , endTime, endTime - startTime);
};
</script>
我凭直觉回答先在控制台打印,但是不知道正确不,于是打开浏览器,试了n多次
发现很难区别哪个更快,渲染DOM和打印几乎同时进行,于是我又把渲染DOM增加到100000个,再次尝试
<div id="app"></div>
<script>
window.onload = function () {
let startTime = +new Date();
let oApp = document.getElementById('app');
for(let i=0; i<100000; i++) {
let oP = document.createElement('p');
oP.innerHTML = i;
oApp.appendChild(oP);
}
let endTime = +new Date();
console.log('输出时间', startTime , endTime, endTime - startTime);
};
</script>
这次效果就比较明显,控制台先打印出秒数,然后页面上DOM进行渲染
但是这种好像跟我们预期的效果不一样,正常开发来说,顺序应该是:先
在页面上渲染出数据
,然后
再在控制台中进行其他操作
。
这时候我们该怎么办呢?没错,setTimeout
就可以帮助我们解决。
将console.log
用setTimeout
包裹,然后我们看一下效果
<div id="app"></div>
<script>
window.onload = function () {
let startTime = +new Date();
let oApp = document.getElementById('app');
for(let i=0; i<100000; i++) {
let oP = document.createElement('p');
oP.innerHTML = i;
oApp.appendChild(oP);
}
let endTime = +new Date();
setTimeout(function(){
console.log('输出时间', startTime , endTime, endTime - startTime);
})
};
</script>
很明显,在添加完setTimeout
之后,页面上先渲染DOM元素,然后才在控制台打印出数据
在聊之前,先补充一下进程和线程的概念(面试常考
)
资源分配
的最小单位(是能拥有资源和独立运行的最小单位)调度
的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)注意:浏览器是多进程的
Browser
进程:浏览器的主进程
,只有一个
第三方插件
进程:每种类型的插件对应一个进程,仅当使用时创建
GPU
进程:用于3D绘制等,最多一个
浏览器渲染
进程:页面渲染、脚本执行、事件处理等而在所有进程中,我们关心浏览器渲染的进程
,这里面包含了以下多个线程
:
1、GUI渲染线程:
渲染浏览器界面
,解析HTML、CSS,构建DOM树和CSSOM数,构建RenderObject树,布局Layout和绘制Painting等重绘(Repaint)
或由于某种操作引发回流(reflow)
时,该线程就会执行2、JS引擎线程:(也称为JS内核,负责处理Javascript脚本程序。例如V8引擎)
注意:
GUI渲染
线程与JS引擎
线程是互斥
的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行
。所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞
3、事件触发线程:
归属于浏览器
而不是JS引擎,用来控制事件循环
(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)setTimeOut
时(也可来自浏览器内核的其他线程
,如鼠标点击
、AJAX异步请求
等),会将对应任务添加到事件线程中把事件添加到待处理队列的队尾
,等待JS引擎的处理
注意:
由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理
(当JS引擎空闲时才会去执行)
4、定时触发器线程:
setInterval
与setTimeout
所在线程定时计数器并不是由JavaScript引擎计数
的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)注意:
W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms
。比如我们给setTimeout的第二个参数设置为0
时,会默认为最低标准4ms
5、异步HTTP请求线程:
XMLHttpRequest在连接后
是通过浏览器新开一个线程请求
而对于setTimeout
和setInterval
,我们要清楚:
执行一次回调
循环执行回调
注意:
1、setTimeout(fn, n)
这里的间隔指定时间n
后执行一次回调是指,fn将会在n秒后被推入任务队列
2、setInterval(fn, n)
并不能保证
两个函数之间调用的间隔
一定是指定的时间n
,如果两个周期的间隔为2n,但是函数fn执行时间大于n,这就会使时间间隔小于n,依次往后,会影响整体流程
3、setInterval(fn, n)
在每次把任务 push 到任务队列时,都要进行一下判断,看上次的任务是否仍在队列中,如果有则不添加,没有则添加
所以,关于文章开头的拿到趣味题目,通过上述浏览器线程的知识,就可以解释清楚了。