技术实践

关于JavaScript操作DOM是同步还是异步

js常见能够改变执行队列的`Promise、setTimeout、`标签等等都没有在这里出现,所以确认是同步执行无疑。 既然同步执行为什么

羊先生的头像
羊先生2020.04.25 · 7 分钟阅读 · 135 阅读
关于JavaScript操作DOM是同步还是异步封面
文章正文还需 7 分钟

最近公司项目大多数还停留在jQuery的年代,所以难免不了去操作DOM,发此记录一下关于JS操作DOM

操作DOM的栗子

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script>
	    var body = document.querySelector('body');
	    console.log('羊先生');
	    var cDiv = document.createElement('div');
	    console.log(cDiv)
	    console.log('vipbic');
	    body.appendChild(cDiv)
	    console.log(body);
	</script>
</body>
</html>

以上结果目前和我们预想的结果是一致的,自上而下依次同步执行,这里划重点,js引擎线程

增加阻塞

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<style>
		.easy {
			width: 200px;
			height: 200px;
			background: pink;
		}
		.hard {
			background: yellow;
			transition: 2s all;
		}
	</style>
	<script>
	    var body = document.querySelector('body');
	    console.log('羊先生');
	    var cDiv = document.createElement('div');
	    console.log(cDiv)
	    console.log('vipbic');
	    body.appendChild(cDiv)
	    console.log(body);
	    cDiv.classList.add('easy')
	    for(var i = 0;i<300000000;i++){

	    }
		cDiv.classList.add('hard')
		console.log(cDiv)
	</script>
</body>
</html>
classList

html5新增API,classList 属性返回元素的类名,作为 DOMTokenList 对象,在线列子

效果如下

可以看到,刷新浏览器,理论上在阻塞的情况下

应该的背景色是粉色吧,但是实际情况是黄色是直接显示的,这里就要涉及到GUI渲染线程

关于浏览器渲染进程多线程

GUI渲染线程

负责渲染浏览器界面,解析HTMLCSS、构建DOM树和RenderObject树,布局和绘制等
当界面需要重绘(Repaint)或由于某种操作导致重排(Reflow)时,该线程就会执行
GUI渲染线程和JS引擎线程是互斥的,当js引擎执行时GUI线程会被挂起(相当于冻结),GUI更新会被保存在一个队列中等到js引擎空闲时立即执行

JS引擎线程

也称之为js内核,负责处理javascript脚本程序(V8引擎)
JS引擎线程负责解析javascript脚本,运行代码
JS引擎一直在等待着任务的到来,然后加以处理,一个renderer进程中无论如何都只有一个js线程在运行js程序
由于GUI渲染线程和JS引擎线程是互斥的,所以如果JS执行时间如果过长,这样就会造成页面渲染不连贯,导致页面渲染加载阻塞

事件触发线程

归属于浏览器而不是js引擎,用来控制事件循环
当js引擎执行代码,如setTimeout时(也可以是来自浏览器内核的其他线程,如鼠标点击,ajax请求等),会将对应的任务添加到事件线程当中
当对应的事件符合触发条件被触发时,事件线程会把事件添加到待处理事件队列的队尾,等待js引擎的处理
由于js的单线程关系,所以这些待处理队列中的事件都得排队等待js引擎处理(当js引擎空闲时才会去执行)

定时器触发器线程

setIntervalsetTimeout所在的线程
浏览器定时计数器并不是由js引擎计数的(因为js是单线程的,如果处于阻塞状态就会影响计数的准确性)
单独的线程来计时并触发定时(计时完毕后,添加到事件队列中,等待js引擎空闲后执行)
W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms

异步http请求线程

XMLHttpRequest在连接后通过浏览器开一个线程请求
将检测到状态变更时,如果有设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再有js引擎执行
这里说的是主流的浏览器渲染进程的主要几个线程,但不囊括所有,如果遇到相应的问题,欢迎评论。

总结

js常见能够改变执行队列的Promise、setTimeout、<script>标签等等都没有在这里出现,所以确认是同步执行无疑。

既然同步执行为什么会有"异步"的效果,这里要说到上文划重点内容: js引擎线程与GUI渲染线程。也就是说,js引擎线程与GUI渲染线程互斥,这是线程之间的"同步"造成的操作DOM时的"异步"效果。

<div>的样式为什么没有生效呢?明明有一个过渡效果。原因是:浏览器的渲染时会执行优化策略,即将多个同一DOM下的样式合并后渲染

虽然DOM操作是同步的,而你期望的「DOM操作」却不一定是同步的,包括调用外设(外设要看具体设备,有的设备会阻塞浏览器执行,什么意思,就是浏览器的异步操作也会停止,结合这里的异步操作的理解,就可以解释一些奇怪现象了)等,需要在日常开发的时候注意

更多文章

浏览器渲染进程多线程
JS中操作DOM是-同步-还是-异步?