浏览器渲染机制
Document Object Model (DOM)
当浏览器读取
HTML
代码时,只要遇到body、div
等HTML
元素,就会创建一个名为 Node 的JavaScript
对象。浏览器从 HTML 文档中创建了
Node
之后,就要把这些节点对象创建成树状结构。CSS Object Model (CSSOM)
是一种用于描述和操作CSS样式表的模型。它将CSS样式表表示为一个对象树,每个对象代表样式表中的一个规则或者样式信息。CSSOM 允许开发人员通过编程方式读取和操作CSS样式表,包括修改样式规则、查询元素的计算样式等。
在W3C标准中,它包含两个部分:描述样式表和规则等CSS的模型部分(CSSOM),和跟元素视图相关的View部分(CSSOM View)。
他提供了一些api包括:
规则部分,支持对元素属性的修改
document.styleSheets[0].insertRule("p { color:pink; }", 0)
document.styleSheets[0].removeRule(0)
视图部分窗口 API
窗口API用于操作浏览器窗口的位置、尺寸等
- moveTo(x, y) 窗口移动到屏幕的特定坐标;
滚动 API
视口滚动API
scrollX 是视口的属性,表示X方向上的当前滚动距离
元素滚动API
scrollTop 元素的属性,表示Y方向上的当前滚动距离。
布局API
这是整个CSSOM中最常用到的部分,我们同样要分成全局API和元素上的API。
全局尺寸信息
如window.innerHeight, window.innerWidth 这两个属性表示视口的大小。
元素的布局信息
所以我们获取宽高的对象应该是“盒”,于是CSSOM View为Element类添加了两个方法:
- getClientRects();会返回一个列表,里面包含元素对应的每一个盒所占据的客户端矩形区域,这里每一个矩形区域可以用 x, y, width, height 来获取它的位置和尺寸。
- getBoundingClientRect()。
Render Tree
Render-Tree
也是一个将DOM
树和CSSOM
树组合在一起构建的树状结构。渲染顺序
布局操作
首先浏览器创建每个单独的
Render-Tree
节点的布局。布局包括每个节点的大小(以像素为单位)和它将被打印在屏幕上的位置。这个过程被称为布局,因为浏览器正在计算每个节点的布局信息。绘制操作
由于
Render-Tree
中的元素(或子树)可以相互重叠,而且它们可以具有CSS
属性,使它们经常改变外观、位置或几何显示(如动画),因此浏览器会为它创建一个图层。创建图层可以帮助浏览器在网页的整个生命周期中高效地执行绘制操作,比如在滚动或调整浏览器窗口大小的时候。拥有图层还可以帮助浏览器按照开发者的意图,正确地按照堆叠顺序(沿 z 轴)绘制元素。
现在我们有了图层,我们可以将它们组合起来,并在屏幕上绘制。但是浏览器并不是一次性绘制所有的图层。每个图层都是先单独绘制的。
在每个图层里面,浏览器会对元素的任何可见属性,如边框、背景色、阴影、文字等进行单独的像素填充。
合成操作
我们拥有的是不同的图层(位图图像),它们应该按照特定的顺序绘制在屏幕上。在合成操作中,这些图层会被发送到 GPU 上,最终将其绘制在屏幕上。
每次回流(布局)或重绘时都要将整个图层送去绘制,这显然是很低效的。因此,一个图层被分解成不同的块,然后将其绘制在屏幕上
细节
细节一:script会阻塞dom的解析
如果它是一个嵌入式
script
,那么它将首先执行该script
,然后继续解析 HTML,构建 DOM 树。所以所有的嵌入式script
都是解析器阻塞型的如果
script
元素是外部script
文件,浏览器会在主线程之外开始下载外部script
文件,但在该文件下载完毕之前,会停止主线程的执行。这意味着在script
文件下载之前,不会再进行 DOM 解析。
async
属性,当DOM
解析器遇到一个带有async
属性的外部script
元素时,它不会在后台下载script
文件时停止解析过程。但是一旦文件下载完毕,解析过程就会停止,script
(代码)就会被执行。
defer
属性,它的工作原理与async
属性类似,但与async
属性不同的是,即使文件完全下载完毕,script
也不会执行。一旦解析器解析了所有的 HTML,也就是说 DOM 树已经完全构建完成,所有的defer
script
就会被执行。与async
不同的是,所有的延迟script
是按照它们在 HTML 文档(或 DOM 树)中出现的顺序来执行的。细节二:css会阻塞dom的解析
DOM 和 CSSOM 树的构建都发生在主线程上,而且这些树的构建是同时进行的。它们共同构成了用于在屏幕上打印东西的 Render Tree,而 Render Tree 也随着 DOM 树的构建而逐步构建。
我们已经了解到,DOM 树的生成是增量的,这意味着当浏览器读取 HTML 时,它会将 DOM 元素添加到 DOM 树中。但 CSSOM 树却不是这样。与 DOM 树不同,CSSOM 树的构建不是递增的,必须以特定的方式进行。
因此,如果浏览器在解析样式表内容时开始递增地构建 CSSOM,就会导致渲染树的多次渲染,因为样式覆盖规则会使同样的 CSSOM 节点,因后面新出现的样式表文件而导致更新。当 CSS 被解析时,可以在屏幕上看到元素样式的改变,这将是一种不愉快的用户体验。由于 CSS 样式是层叠的,一个规则的改变可能会影响许多元素。
因此,浏览器不会逐步处理外部 CSS 文件,CSSOM 树更新是在样式表中所有 CSS 规则处理完毕后一次性完成的。CSSOM 树更新完成后,再更新渲染树,然后渲染到屏幕上。
CSS 是一种渲染阻塞型资源。一旦浏览器提出获取外部样式表的请求,Render Tree 的构建就会停止。因此,关键渲染路径(CRP)也被卡住了,没有任何东西被渲染到屏幕上,如下图所示。然而,在后台下载样式表时,DOM 树的构建仍在进行中。
但是一旦样式表被下载和解析,导致 CSSOM 更新,我们的 JavaScript 现在有一个过时的元素的 CSS 值,因为新的 CSSOM 更新可能已经改变了该 DOM 元素的 CSS 属性。由于这个原因,在下载样式表的时候执行 JavaScript 是不安全的。
浏览器会等到样式表被加载和解析。一旦样式表被解析,CSSOM 被更新,Render Tree 就会被更新,CRP 就会继续进行,从而使 Render Tree 绘制在屏幕上。由于这个原因,建议尽早加载所有外部样式表。
node高并发机制
事件驱动+事件循环实现高并发
执行顺序:
1、每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈(execution context stack)
2、主线程之外,还维护了一个"事件队列"(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
3、主线程代码执行完毕完成后,然后通过Event Loop,也就是事件循环机制,开始到Event Queue的开头取出第一个事件,从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。
注意
我们所看到的node.js单线程只是一个js主线程与ui渲染共享一个线程,本质上的异步操作还是由线程池完成的,node将所有的阻塞操作都交给了内部的线程池去实现,本身只负责不断的往返调度,并没有进行真正的I/O操作,从而实现异步非阻塞I/O,这便是node单线程和事件驱动的精髓之处了。
总结:
1、libuv 线程池默认打开 4 个,最多打开 128个 线程。(例如:以前 web 服务器同一时间比如说最多只能接收 100 个请求,多的就无法接收了,服务器就挂掉了。nodejs 所谓的高并发是指可以同时接收 1000、10000 个请求,只不过以排队的方式在等待。)
2、主线程执行js,是单线程的,js代码在做大量计算就是cpu密集了。主线程不空闲出来也没法处理 io 的事,所以就会阻塞了。