浏览器渲染机制和node事件循环

浏览器渲染机制

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用于操作浏览器窗口的位置、尺寸等

  1. moveTo(x, y) 窗口移动到屏幕的特定坐标;
滚动 API

视口滚动API

scrollX 是视口的属性,表示X方向上的当前滚动距离

元素滚动API

scrollTop 元素的属性,表示Y方向上的当前滚动距离。

布局API

这是整个CSSOM中最常用到的部分,我们同样要分成全局API和元素上的API。

全局尺寸信息

如window.innerHeight, window.innerWidth 这两个属性表示视口的大小。

元素的布局信息

所以我们获取宽高的对象应该是“盒”,于是CSSOM View为Element类添加了两个方法:

  1. getClientRects();会返回一个列表,里面包含元素对应的每一个盒所占据的客户端矩形区域,这里每一个矩形区域可以用 x, y, width, height 来获取它的位置和尺寸。
  2. 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的事件循环

浏览器中的事件循环是根据 HTML 标准实现的,而 Node.js 中的事件循环则是基于 libuv 实现的。

libuv 是一个用 C 语言实现的高性能解决单线程非阻塞异步 I/O 的开源库,本质上它是对常见操作系统底层异步 I/O 操作的封装。

而在 nodejs 中,这都是 libuv 完成的。

几乎每个 Node API 都有异步执行版本libuv 直接负责它们的执行,libuv 会开启一个线程池,主线程执行到异步操作后,libuv 就会在线程池中调度空闲线程去执行,可以说 libuv 为 nodejs 提供了整个事件循环功能。

过程

与在浏览器中一样,在 nodejs 中 JS 最开始在主线程上执行,执行同步任务、发出异步请求、规划定时器生效时间、执行 process.nextTick 等,这时事件循环还没开始。

在上述过程中,如果没有异步操作,代码在执行完成后便直接退出。如果有,libuv 会把不同的异步任务分配给不同的线程,形成事件循环。在同步代码执行完后,nodejs 便会进入事件循环,依次执行不同队列中的任务。

Nodejs 事件循环中的消息队列共有 8 个,若引用之前宏队列、微队列的说法,具体可划分为:

  • 宏队列
    • timers (重要),计时器队列,负责处理 setTimeout 和 setInterval 定义的回调函数。
    • pending callback (待定回调)
      • 调用上一次事件循环没在 poll 阶段立刻执行,而延迟的 I/O 回调函数
    • idle prepare (空闲准备)
      • 仅供 nodejs 内部使用
    • poll (重要),轮询队列,该阶段会处理除 timers 和 check 队列外的绝大多数 I/O 回调任务,如文件读取、监听用户请求等。
    • check (重要),检查队列,负责处理 setImmediate 定义的回调函数。
    • close callbacks
      • 执行所有注册 close 事件的回调函数
  • 微队列
    • nextTick
    • Promise

对于微队列的 nextTick 和 Promise,严格意义上讲也不属于事件循环。在事件循环中,每次打算进入下个阶段之前,必须要先依次反复清空 nextTick 和 promise 队列,直到两个队列完全没有即将要到来的任务的时候再进入下个阶段。

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 的事,所以就会阻塞了。

  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值