1、浏览器的功能划分
浏览器有两个重要功能,一个是渲染引擎,专门用来渲染 HTML 和 CSS 的,另一个是 JS 引擎,专门用来解析执行 JavaScript 的。上述的两个引擎是在不同的线程中的,这两个引擎是互不相干的,如果需要通过 JavaScript 来操作 dom,就会涉及到两个线程之间的通信,这就是为什么会有 dom 操作很慢的说法。
实际上 dom 操作只是相对于 JavaScript 的解析执行来说比较慢,如果相比 Ajax 请求的话来说还是比较快的。
2、跨线程 dom 操作
浏览器的渲染引擎和 JS 引擎各司其职,互不相干,JS 引擎不能操作页面,只能操作 JS,渲染引擎不能操作 JS,只能操作页面。如果 JavaScript 中有这样一行操作 dom 的代码:document.body.appendChild(div);,那页面是如何改变的呢?
按照上述说法,渲染引擎和 JS 引擎互不相干,那 div 对象应该只是存在内存中的,document.body.appendChild(div);这条语句又为何能将 div 渲染到页面上的呢?这其中是浏览器起到的一个通信桥梁的作用:当浏览器发现 JS 在 body 里面加了个 div 对象,浏览器就会通知渲染引擎在页面里也新增一个 div 元素,新增的 div 元素所有属性都照抄 JavaScript 中的 div 对象。
3、插入新标签的完整过程
let div = document.createElement('div'); div.innerText = 'hi'; document.body.appendChild('div'); //将div添加到body中 div.innerText = 'frank';
如上述操作,在 div 被加入页面之前,创建 div 和修改 div 的 innerText 的操作都属于JS 线程内的操作,不涉及渲染操作。当把 div 放入页面之时,浏览器会发现 JS 的意图,就会通知渲染线程在页面中渲染出一个 div 元素,该元素的属性都照抄 JS 中的 div 对象。
当 div 元素被渲染到页面后,对 div 的 innerText 属性做出的修改时,页面会重新渲染。页面的重新渲染操作并不是和 JS 引擎中的具体操作一一对应的,比如在修改 div 的 id,如果修改后的 id 没有添加 css 样式,则页面不会重新渲染,如果添加了 css 样式,那就会重新渲染。
如果连续对 div 进行多次操作,浏览器 可能 会合并成一次操作。举个栗子,如下的读写操作,三个写的操作就会合并成,页面只进行一次重新渲染。但如果是读写间隔的代码的话,页面就会重新渲染三次,因为这里涉及一个属性 clientHeight,这个属性是需要计算得到的,会触发浏览器的一次 layout 布局。
// Read var h1 = element1.clientHeight; var h2 = element2.clientHeight; var h3 = element3.clientHeight; // Write (invalidates layout) element1.style.height = (h1 * 2) + 'px'; element2.style.height = (h2 * 2) + 'px'; element3.style.height = (h3 * 2) + 'px';