前端roadmap_浏览器工作原理

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
.cn 域名,1个 12个月
简介: • 浏览器架构总览• 进程、线程• 站点隔离• 渲染流程总览• 导航阶段• UI进程👉拼装URL• 网络进程👉获取数据• 重定向• 根据Content-Type进行数据处理• 唤起渲染进程• 更新Tab状态 -->导航阶段结束• 渲染阶段• 编译处理• BNF• HTML 解析器• DOM• 标记算法• DOM树构建算法• 处理子资源• 将CSS附加(attachment)到DOM节点==>生成Render Tree• CSS 解析器• 创建呈现器• 属性标准化• 样式计算并保存到ComputedStyle• 布局(Layout)


概要


前提概要

在今年第一篇文章中,我们已经讲述过,我们以后的行文路线会按照前端 Roadmap 的进行。

我们今天来简单介绍一下行文路径中 Browser and how they work - 浏览器是如何运行的

根据权威机构统计调查,常规的主流浏览器在全球范围内所在的比重如下图所示。

所以,本文以Chrome浏览器来展示浏览器的工作流程。

浏览器架构总览

进程、线程

在开始介绍浏览器工作流程的时候,我们需要简单说一下,进程线程

进程:某个应用程序的执行程序。

线程:常驻在进程内部并负责该进程部分功能的执行程序。

当你启动一个应用程序,对应的进程就被创建。进程可能会创建一些线程用于帮助它完成部分工作,新建线程是一个可选操作。在启动某个进程的同时,操作系统(OS)也会分配内存以用于进程进行私有数据的存储。该内存空间是和其他进程是互不干扰的。

当应用程序被关闭,进程也随之关闭,同时OS会将进程所占的内存释放掉。

其实这是一个动图,由于掘金没法嵌入 svg 格式的图片,video 也不行。所以,如果想看流程可以通过 传送门进行了解。

有人的地方就会有江湖,如果想让多人齐心协力的办好一件事,就需要一个人去统筹这些工作,然后通过大喇叭将每个人的诉求告诉对方。而对于计算机而言,统筹的工作归OS系统负责,OS通过Inter Process Communication (IPC)的机制去传递消息。

其实这是一个动图,由于掘金没法嵌入 svg 格式的图片,video 也不行。所以,如果想看流程可以通过 传送门进行了解。

下面,我们来看看Chrome架构是如何组织的。

Chrome 的默认策略是,每个标签对应一个Render Process。但如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程。官方把这个默认策略叫 process-per-site-instance


同一站点根域名加上协议,还包含了该根域名下的所有子域名和不同的端口

A.fake.com

www.fake.com

B.fake.com:789789

这三个域名就是同一站点。


站点隔离


我们之前介绍浏览器渲染进程 -> 一般情况下,一个Tab页对应一个渲染进程。这里存在一个漏洞,页面中存在跨域的 iframe,该 iframe 就会有访问该渲染进程内存的权限,这就违背了,同源策略。 所以,在最新的chrome架构中,如果页面中存在跨域的 iframe,这些跨域的 iframe也会唤起一个属于自己的渲染进程


渲染流程总览

虽然,该篇文章以 chrome浏览器来解释 浏览器的工作流程,但是我们还是需要对其他浏览器的渲染进程配置做一个汇总。


导航阶段

浏览器的导航过程涵盖了从用户发起请求提交文档给渲染进程的中间所有阶段。

UI进程👉拼装URL

当用户在地址栏中输入一个查询关键字时,地址栏会判断输入的关键字是搜索内容,还是请求的 URL。

  • 如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的 URL。
  • 如果判断输入内容符合 URL 规则,那么地址栏会根据规则,把内容加上协议,合成为完整的 URL。


网络进程👉获取数据

当最终的URL拼装好后,会由UI进程通过IPC通知浏览器进程,而浏览器进程会将消息传递给网络进程。(此时的浏览器进程充当一个消息中转站的角色) 当网络进程接受到来自UI进程需要网络连接的消息后。随之会初始化一个网络连接。

该篇幅主要是讲解,浏览器如何渲染数据,所以浏览器如何和服务器建立连接等步骤,会一带而过。

首先,网络进程会查找本地缓存是否缓存了该资源。如果有缓存资源,那么直接返回资源给浏览器进程;如果在缓存中没有查找到资源,那么直接进入网络请求流程。这请求前的第一步是要进行 DNS 解析,以获取请求域名的服务器 IP 地址。如果请求协议是 HTTPS,那么还需要建立 TLS 连接。

接下来就是利用 IP 地址和服务器建立 TCP 连接。连接建立之后,浏览器端会构建请求行、请求头等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,然后向服务器发送构建的请求信息。

服务器接收到请求信息后,会根据请求信息生成响应数据(包括响应行、响应头和响应体等信息),并发给网络进程。等网络进程接收了响应行和响应头之后,就开始解析响应头的内容了。

重定向

在接收到服务器返回的响应头后,网络进程开始解析响应头,如果发现返回的状态码是 301永久性的移动) 或者 302临时性重定向),那么说明服务器需要浏览器重定向到其他 URL。这时网络进程会从响应头的 Location字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,一切又重头开始了。

在导航过程中,如果服务器响应行的状态码包含了 301、302 一类的跳转信息,浏览器会跳转到新的地址继续导航;如果响应行是 200,那么表示浏览器可以继续处理该请求。

根据Content-Type进行数据处理

Content-Type: XX => XX是MIME类型的字符格式。MIME指示文档、文件或字节分类的性质和格式。

Content-Type 告诉浏览器服务器返回的响应体数据是什么类型,然后浏览器会根据 Content-Type 的值来决定如何显示响应体的内容。

不同 Content-Type 的后续处理流程也截然不同。如果 Content-Type 字段的值被浏览器判断为下载类型,那么该请求会被提交给浏览器的下载管理器,同时该 URL 请求的导航流程就此结束。但如果是Content-Type:text/html,那么浏览器则会继续进行导航流程。


唤起渲染进程

由于,浏览器和服务器之间数据的交互时间存在不确定性,在响应体回来以后,再唤起渲染进程是存在滞后的。

所以在UI进程向网络进程发送拼装好的URL的时候,已经知道后续的导航的信息。此时UI进程会尝试随机唤起一个渲染进程。以备在响应体数据满足渲染要求,直接进行渲染操作。

UI进程向网络进程发送URL 和 UI进程尝试唤起渲染进程是 同步进行的。

更新Tab状态 -->导航阶段结束

在响应数据和渲染进程准备就绪后,网络进程通过IPC向渲染进程传递提交响应体数据的消息

  • 渲染进程和网络进程会建立数据通道。网络进程的数据就会源源不断的流向渲染进程。
  • 当数据都传送完毕后,渲染进程会向UI进程发送确认提交的消息。
  • UI进程在接收到确认提交消息,更新浏览器界面状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新 Web 页面。

此时导航阶段正式结束,接下来进入页面渲染阶段。


渲染阶段

下面我们来重点讲述下,Chrome浏览器是如何将 HTMLCSS拼装成浏览器可识别的信息。

想必大家都见过这张图,该图显示的是 WebKit 内核的渲染页面主流程。之所以,我把这个拿出来,是因为 Chrome/Safari/Edge 的渲染引擎都是 基于 Webkit 改造而来的。所以,我们通过了解 Webkit 的渲染流程,就能通晓市面上大部分浏览器的运行流程。

而上面所展示的图,是 2011 年所绘制的,现在都 1202 年了,有些流程和细节有变更和填充。但是,大体的流程和处理方式是一脉相承的。


我们通过导航阶段,从服务器中获取到用于浏览器展示的数据信息-- HTML/CSS。但是,HTML和CSS都是文本信息,是无法被浏览器所识别和使用,所以就需要一个机制,让HTML等文本信息转换为浏览器能够识别的格式,而这个转换过程就是解析阶段。

HTML和(CSS/JS)的解析方式是不一样的。

编译器

大多数编译程序(compiler)分为三个步骤:Parsing(分析阶段)/Transformation(转换)/Code Generation(代码生成或者说生成目标代码)

  • Parsing将源代码(raw code)通过词法分析语法分析转换为AST(抽象语法树)。
  • Transformation接收Parsing生成的AST,并且按照compiler内定的规则进行代码的转换。
  • Code Generation 接受被compiler转换过的代码,按照一定的规则将代码转换为最终想要输出的代码格式

通过以上三个步骤,大部分程序会被编译为目标代码。如果想了解更多关于编辑器是如何运行的,可以参考我原来写的编译程序(compiler)的简单分析。 在这里就不多啰嗦了。


BNF

常规的与上下文无关的语言,是可以通过 BNF 格式来描述。

BNF: 一种 形式化符号 来 描述 给定语言 的 语法

一种形式化的语法表示方法,用来描述语法的一种形式体系,是一种典型的元语言。


它不仅能严格地表示语法规则,而且所描述的语法是与上下文无关的。它具有语法简单,表示明确,便于语法分析和编译的特点。


BNF表示语法规则的方式为:

①:非终结符用尖括号括起。

②:每条规则的左部是一个非终结符,右部是由非终结符和终结符组成的一个符号串,中间一般以::=分开。

③:具有相同左部的规则可以共用一个左部,各右部之间以直竖“|”隔开。

例如,我们在解析 2 + 3 - 1 这个表达式时,

词法规则,我们可以用:

INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -
复制代码

语法规则

expression ::=  term  operation  term
operation ::=  PLUS | MINUS
term ::= INTEGER | expression
复制代码

生成的 AST 结构

通过对AST进行个性化处理,最终生成指定机器和引擎能够识别的机器语言。

在讲述BNF是啥的时候,我们提到了 与上下文无关这个概念。根据 维基百科的描述,

我们可以简单描述下,在常规的语言中,如果是CFG的话,是可以描述为 ,而 是一个非终端符号或者标识,一般为终止符号或者说是Tokens(不可分割元素)。


而一个语言,不需要考虑左值的上下文语境的时候,就可以使用BNF来表示。


HTML 解析器

既然,我们说HTML想要被浏览器识别,是需要被解析的,而由于HTML的语言特性或者独特的解析过程,HTML是不能使用常规 上下文无法的编译器进行转换的。

理由如下:

  • 语言的宽容本质
  • 浏览器历来对一些常见的无效 HTML 用法采取包容态度
  • 解析过程需要不断地反复。源内容在解析过程中通常不会改变,但是在 HTML 中,脚本标记如果包含 document.write,就会添加额外的标记,这样解析过程实际上就更改了输入内容

DOM

而HTML解析器最终的目标就是为了,将HTML转换为浏览器能识别的数据结构 -- DOM

DOM(Document Object Model)的缩写,即文档对象模型。是针对XML并经过扩展用于HTML的应用程序编程接口(API)


所以DOM本质上是一种接口(API),是专门操作网页内容的API标准


DOM把整个页面映射为一个多层节点结构,HTML或XML页面中的每个组成部分都是某种类型的节点。借助DOM提供的API,开发人员可以删除、添加、替换或修改任何节点

由于不能使用常规的解析技术,浏览器就创建了自定义的解析器来解析 HTML。

此算法由两个阶段组成:标记化树构建


标记算法

标记化是词法分析过程,将输入内容解析成多个标记(tokens)。HTML 标记包括起始标记、结束标记、属性名称和属性值。

这里有一个点需要注意:在 HTML2 -HTML4 中, 声明引用 DTD,因为 HTML 4.01 基于 SGMLDTD 规定了标记语言的规则(tokens),这样浏览器才能正确地呈现内容。




HTML5 不基于 SGML,所以不需要引用 DTD。


DOM树构建算法

标记生成器识别标记,传递给树构造器,然后接受下一个字符以识别下一个标记;如此反复直到输入的结束。

在创建解析器的同时,也会创建 Document 对象。在树构建阶段,以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。标记生成器发送的每个节点都会由树构建器进行处理。规范中定义了每个标记所对应的 DOM 元素在接收到相应的标记时创建

处理子资源

网站中总是会嵌入图片CSSJS非文本资源,而这些非文本信息需要再次从服务器或者缓存中获取。在DOM树构建过程中,如果遇到此类HTML标签,主线程将会依次请求对应的数据信息。而为了加快构建速度,预加载扫描器会和构建DOM树同步运行。 在标记化过程中,如果遇到类似或者标签,预加载扫描器会通知网络进程发起获取对应标签数据信息的异步请求。(此时主线程和网络请求是同步的)

一切都归于完美,但是如果非文本标签是是</code>,就是另外一回事了。当标记算法输出的是<code><script></code>,此时HTML的解析过程就会停止,也就是说,主线程不会在继续解析tokens,转而去加载、解析、执行对应的JS代码。只有在JS代码执行完成以后,HTML的解析才会继续进行。</div><blockquote style="background-color: #F8F8F8;"><div><strong>JS会阻塞DOM树的构建</strong> -->是由于JS代码中可能掺杂着类似于<code>document.write()</code>这种对于已经构建好的DOM树来说属于毁灭性打击的操作(直接将原来的树,全部抛弃)。</div></blockquote><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_43e468df999d4ab69f20507a9323256c.png%22%2C%22originWidth%22%3A308%2C%22originHeight%22%3A400%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A308%2C%22height%22%3A400%7D"></span></div><div>由于<code><script></code>会阻塞主线程构建DOM树,所以如果<code><script></code>中不存在<code>document.write()</code>这种对已构建DOM树毁灭性打击的行为,我们可以通过对<code>script</code>设置<code>defer</code>/<code>async</code>属性来避免阻塞。</div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%3Cscript%20async%20src%3D%5C%22A.js%5C%22%3E%3C%2Fscript%3E%5Cn%E5%A4%8D%E5%88%B6%E4%BB%A3%E7%A0%81%22%2C%22id%22%3A%22f12bu%22%7D"></div><div>有 <code>async</code>,加载和渲染后续文档元素的过程将和 A.js 的加载与执行<code>并行</code>进行(<strong>异步</strong>)。</div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22%3Cscript%20defer%20src%3D%5C%22B.js%5C%22%3E%3C%2Fscript%3E%5Cn%E5%A4%8D%E5%88%B6%E4%BB%A3%E7%A0%81%22%2C%22id%22%3A%22hog1l%22%7D"></div><div>有 <code>defer</code>,加载后续文档元素的过程将和 B.js 的加载并行进行(<strong>异步</strong>),但是 B.js 的执行要在<strong>所有元素解析完成</strong>之后,<code>DOMContentLoaded</code> 事件触发<strong>之前</strong>完成。</div><div>从实用角度来说呢,首先把<strong>所有脚本</strong>都丢到 <code></body></code> 之前是最佳实践,因为对于<strong>旧浏览器</strong>来说这是<code>唯一</code>的优化选择,此法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。 接着,我们来看一张图咯: <span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_b0d785f5e9044feaaf431f811c1de058.png%22%2C%22originWidth%22%3A689%2C%22originHeight%22%3A112%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A689%2C%22height%22%3A112%7D"></span>蓝色线代表网络读取,红色线代表执行时间,这俩都是<strong>针对脚本</strong>的;绿色线代表 HTML 解析。</div><div>此图告诉我们以下几个要点: <code>defer</code> 和 <code>async</code> 在网络读取(下载)这块儿是一样的,都是<strong>异步</strong>的(相较于 HTML 解析) 它俩的差别在于脚本下载完之后<strong>何时执行</strong>,显然 defer 是最接近我们对于应用脚本加载和执行的要求的</div><blockquote style="background-color: #F8F8F8;"><div>Note: <code>defer</code> 是按照加载顺序执行脚本的, <code>async</code> 是乱序执行的。</div></blockquote><div>同时,我们可以通过<code><link preload/></code>对资源进行预加载处理。 具体如何使用,可以参考 <a href="https://link.juejin.cn/?target=https%3A%2F%2Fwww.cnblogs.com%2Fshenjp%2Fp%2F13029185.html" target="_blank" ref="nofollow noopener noreferrer">通过link的preload进行内容预加载</a>,描述的很详细。或者对性能优化感兴趣,可以参考外文 <a href="https://link.juejin.cn/?target=https%3A%2F%2Fweb.dev%2Ffast%2F%23prioritize-resources" target="_blank" ref="nofollow noopener noreferrer">Fast load times</a>(后续有计划做整理和翻译,敬请期待)</div><div>经过一顿操作,HTM解析器终于将HTML转化为浏览器能够识别的 <code>DOM</code> 结构。</div><div>通过对百度首页渲染流程来简单看一下。 <span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_e0979c2585494093b6bc7967ba163616.png%22%2C%22originWidth%22%3A2202%2C%22originHeight%22%3A1556%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A2202%2C%22height%22%3A1556%7D"></span></div><blockquote style="background-color: #F8F8F8;"><div>最下方是显示有一个棕色线条 和用小圆圈标注的调用堆栈,表示DOM构建的过程。从图中可以看出,DOM是在HTM 解析阶段生成。</div></blockquote><div data-card-type="block" data-ready-card="hr"></div><h3 id="jJSSi"><br /></h3><h3 id="ZYYEe"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?target=" target="_blank">将CSS附加(attachment)到DOM节点==>生成Render Tree</a></h3><div><br /></div><div><strong>解析样式</strong>和<strong>创建呈现器</strong>的过程称为“<strong><code>附加</code></strong>”。每个 DOM 节点都有一个“<code>attach</code>”方法。附加是<strong>同步</strong>进行的,将节点插入 DOM 树需要调用新的节点“attach”方法。</div><div>处理 <code>html</code> 和 <code>body</code> 标记就会构建呈现树<strong>根节点</strong>。这个根节点呈现对象对应于 CSS 规范中所说的容器 <code>block</code>,这是最上层的 block,包含了其他所有 block。它的尺寸就是<strong>视口</strong>,即<strong>浏览器窗口显示区域的尺寸</strong>。 WebKit 称之为 <code>RenderView</code>。这就是文档所指向的呈现对象。呈现树的其余部分以 DOM 树节点插入的形式来构建。</div><h4 id="wznps"><br /></h4><h4 id="UnonW"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?target=" target="_blank">CSS 解析器</a></h4><div><br /></div><div>由于HTML解析和CSS解析都是在渲染进程中,并且渲染进程只存在一个主线程,也就意味着主线程在同一时间只能做一件事。--><strong>单线程特性</strong>。</div><div>然后在DOM构建完成,并且将位置靠前的<code><script></code>也处理完,以后才会开始CSS的解析步骤。 <span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_08a9dea0162d44e980005d4111be8b11.png%22%2C%22originWidth%22%3A2276%2C%22originHeight%22%3A562%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A2276%2C%22height%22%3A562%7D"></span></div><div>由于<code>CSS</code> 是<strong>上下文无关</strong>语言,所以解析CSS可以使用常规的编译器。而<a href="https://link.juejin.cn/?target=https%3A%2F%2Fwww.w3.org%2FTR%2FCSS%2F%23intro" target="_blank" ref="nofollow noopener noreferrer">W3C中CSS</a>定义了相关的<strong>词法和语法</strong>。</div><div>解析器会将 <code>CSS</code> 文件解析成 <code>StyleSheet</code> 对象,且每个对象都包含 <strong>CSS 规则</strong>。CSS 规则对象则包含<strong>选择器</strong>和<strong>声明对象</strong>,以及其他与 CSS 语法对应的对象。</div><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_fb8135e8b56e41b5bbadead9f0bb94d6.png%22%2C%22originWidth%22%3A500%2C%22originHeight%22%3A393%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A500%2C%22height%22%3A393%7D"></span></div><div data-card-type="block" data-ready-card="hr"></div><h4 id="zAXb4"><br /></h4><h4 id="NSqoU"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?target=" target="_blank">创建呈现器</a></h4><div><br /></div><div>这是由<code>可视化元素</code>按照其<strong>显示顺序</strong>而组成的树,也是文档的<strong>可视化表示</strong>。它的作用是按照正确的顺序绘制内容。</div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22class%20RenderObject%7B%5Cn%20%20virtual%20void%20layout()%3B%5Cn%20%20virtual%20void%20paint(PaintInfo)%3B%5Cn%20%20virtual%20void%20rect%20repaintRect()%3B%5Cn%20%20Node*%20node%3B%20%20%2F%2Fthe%20DOM%20node%5Cn%20%20RenderStyle*%20style%3B%20%20%2F%2F%20the%20computed%20style%5Cn%20%20RenderLayer*%20containgLayer%3B%20%2F%2Fthe%20containing%20z-index%20layer%5Cn%7D%5Cn%E5%A4%8D%E5%88%B6%E4%BB%A3%E7%A0%81%22%2C%22id%22%3A%228FguZ%22%7D"></div><div>每一个呈现器都代表了一个<strong>矩形的区域</strong>,通常对应于相关节点的 CSS 框,包含诸如宽度、高度和位置等几何信息。</div><div>框的类型会受到与节点相关的“<code>display</code>”样式属性的影响。例如,针对 <code>display:block</code>的元素,它矩形区域默认独占一行,而 <code>display:inline</code>的元素,是具有包裹性。(其实,针对CSS中盒模型是一个很大的课题,这块可以参考张鑫旭大佬有关的讲解。同时,自己也会有一定的文档说明,最近在做总结和梳理,敬请期待!)</div><h5 id="50KYf"><br /></h5><h5 id="9DKh5"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?target=" target="_blank">属性标准化</a></h5><div><br /></div><div>现在我们已经将 CSS 节点解析为 <code>RenderObject</code>,但是在写CSS的时候,我们会写诸如<code>font-size:2em</code>的条件,而这些<strong>em</strong>是相对值,不是一个定值,所以,我们就需要将如 <code>2em</code>、<code>blue</code>、<code>bold</code>,这些不容易被渲染引擎理解的值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是<strong>属性值标准化</strong>。</div><div>如果存在如下的样式信息</div><div data-card-type="block" data-ready-card="codeblock" data-card-value="data:%7B%22mode%22%3A%22plain%22%2C%22code%22%3A%22body%20%7B%20font-size%3A%202em%20%7D%5Cnp%20%7Bcolor%3Ablue%3B%7D%5Cnspan%20%20%7Bdisplay%3A%20none%7D%5Cndiv%20%7Bfont-weight%3A%20bold%7D%5Cndiv%20%20p%20%7Bcolor%3Agreen%3B%7D%5Cndiv%20%7Bcolor%3Ared%3B%20%7D%5Cn%E5%A4%8D%E5%88%B6%E4%BB%A3%E7%A0%81%22%2C%22id%22%3A%22yfpox%22%7D"></div><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_752a9f488b4b4792a9d230b2cf496e83.png%22%2C%22originWidth%22%3A1142%2C%22originHeight%22%3A346%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A1142%2C%22height%22%3A346%7D"></span></div><h4 id="Uchg2"><br /></h4><h4 id="gbxOI"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?target=" target="_blank">样式计算并保存到ComputedStyle</a></h4><div><br /></div><div>在样式标准化后,渲染引擎已经可以识别每个 <code>RenderObject</code>中所携带真正的数据信息了, 但是DOM 节点和 <code>RenderObject</code> 可能存在<strong>一对多</strong>的关系。所以,我们需要将这些信息进行融合,这样才可以将<strong>最后的样式</strong>信息作用到 DOM 节点上。</div><blockquote style="background-color: #F8F8F8;"><div>产生某个DOM 节点受多个样式影响的原因之一:CSS文件来源不定 </div><div>CSS 样式<strong>来源</strong>主要有三种:</div><div>①: 通过 <code>link</code> 引用的<strong>外部</strong> CSS 文件</div><div>②:<code><style></code>标记内的 CSS</div><div>③:元素的 style 属性<strong>内嵌</strong>的 CSS</div><div><br /></div><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_ffc3b4d1ebfd4a4eb6ed321d4fada9ae.png%22%2C%22originWidth%22%3A1142%2C%22originHeight%22%3A585%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A1142%2C%22height%22%3A585%7D"></span></div></blockquote><div>某个样式属性的声明可能会出现在多个样式表中,也可能在同一个样式表中出现多次。这意味着应用规则的顺序极为重要。这称为“层叠”顺序。根据 CSS3 规范,层叠的顺序为(<strong>优先级从高到低,降序排序</strong>): <span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_82697a67391f4dd08850cd02972c0f9d.png%22%2C%22originWidth%22%3A1850%2C%22originHeight%22%3A902%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A1850%2C%22height%22%3A902%7D"></span>我们可以简化一下,就是</div><ol><li style="padding-left: 6px;">1. CSS3的<code>transition</code>--> 优先级最高</li><li style="padding-left: 6px;">2. 浏览器重要声明 --> user agent stylesheet 中存在<code>!important</code></li><li style="padding-left: 6px;">3. 用户重要声明 --> 用户,就是直接在浏览器写的带有<code>!important</code>的属性</li><li style="padding-left: 6px;">4. 作者重要声明 --> <code><link></code>/<code><style></code>/<code>style</code>属性中带有<code>!important</code>的属性</li><li style="padding-left: 6px;">5. 动画属性</li><li style="padding-left: 6px;">6. 作者普通声明 --> <code><link></code>/<code><style></code>/<code>style</code>属性</li><li style="padding-left: 6px;">7. 用户普通声明 --> 用户设置的自定义样式</li><li style="padding-left: 6px;">8. 浏览器声明 --> <code>user agent stylesheet</code>浏览器默认属性</li></ol><div>相关连接请参考 <a href="https://link.juejin.cn/?target=https%3A%2F%2Fwww.w3.org%2FTR%2F2021%2FWD-css-cascade-5-20210119%2F" target="_blank" ref="nofollow noopener noreferrer">www.w3.org</a> 和 <a href="https://link.juejin.cn/?target=https%3A%2F%2Fdrafts.csswg.org%2Fcss-cascade-4%2F%23important" target="_blank" ref="nofollow noopener noreferrer">css-cascade-4</a>。</div><div>同时,如果不同样式都作用于同一 DOM节点,就需要有一个权重计算的规则。</div><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_3e57207b8d7b4c11a04c63ef80d2581e.png%22%2C%22originWidth%22%3A600%2C%22originHeight%22%3A764%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A600%2C%22height%22%3A764%7D"></span></div><div>一图胜千言,有木有。</div><div>我们通过权重计算等操作,最后可以确定了针对指定DOM 节点所携带的在最终样式信息,而这些信息会被存到 <code>ComputedStyle</code> 结构中。 <span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_27fc57015a4b4618996e0bb4efe89032.png%22%2C%22originWidth%22%3A1612%2C%22originHeight%22%3A644%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A1612%2C%22height%22%3A644%7D"></span>如果在实际开发中,你用过<code>style = window.getComputedStyle(element); </code>该方法,返回了指定element所有的属性。而这个的方法返回的数据信息,其实就是通过一系列计算得到的<code> ComputedStyle</code>结构。</div><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_9c5b263bcc6045568319cdb0f70fcdf5.png%22%2C%22originWidth%22%3A731%2C%22originHeight%22%3A396%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A731%2C%22height%22%3A396%7D"></span>最后,我们得到了,一棵通过向 DOM树 <strong>附加</strong>样式信息的 <code>Render Tree</code>。</div><div data-card-type="block" data-ready-card="hr"></div><h2 id="7Sqdh"><br /></h2><h2 id="577Ep"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?target=" target="_blank">布局(Layout)</a></h2><div><br /></div><div>通过HTML 解析和 CSS 解析,已经将HTML和CSS信息融合到一起,并且知道了每个节点各自的<strong>外观样式</strong>信息。但是光有外观样式信息还是不能够将节点排布到他们真正需要渲染到页面中的位置。还需要该元素对应的位置和大小信息。</div><div>呈现器在创建完成并添加到呈现树时,并<strong>不包含位置和大小信息</strong>。计算这些值的过程称为<strong>布局</strong>或<strong>重排</strong>。</div><div>HTML 采用<strong>基于流</strong>的布局模型,处于流中<strong>靠后位置</strong>元素通常不会影响靠前位置元素的几何特征,因此布局可以按<code>从左至右、从上至下</code>的顺序遍历文档。</div><blockquote style="background-color: #F8F8F8;"><div>坐标系是相对于根框架而建立的,使用的是<code>上坐标</code>和<code>左坐标</code>。</div></blockquote><div>布局是一个<strong>递归</strong>的过程。它从<strong>根呈现器</strong>(对应于 HTML 文档的 元素)开始,然后递归遍历部分或所有的框架层次结构,为每一个需要计算的呈现器计算几何信息。</div><div>根呈现器的位置<strong>左边</strong>是 <code>0,0,</code>其尺寸为视口(也就是<strong>浏览器窗口的可见区域</strong>)。 所有的呈现器都有一个“<code>layout</code>”或者“<code>reflow</code>”方法,每一个呈现器都会调用其需要进行布局的子代的 layout 方法。</div><div>布局是一个寻找元素<strong>几何形状</strong>的过程。主线程遍历DOM和计算样式并创建布局树,布局树包<strong>含诸如x y坐标和边框大小等信息</strong>。呈现器是和 DOM 元素相对应的,但<strong>并非一一对应</strong>。非可视化的 DOM 元素不会插入布局树中,例如“<code>head</code>”元素。如果元素的 <code>display</code> 属性值为“<code>none</code>”,那么也不会显示在呈现树中(但是 visibility 属性值为“<code>hidden</code>”的元素仍会显示)。</div><blockquote style="background-color: #F8F8F8;"><div>有一些呈现对象对应于 DOM 节点,但在树中所在的位置与 DOM 节点不同。<code>浮动定位</code>和<code>绝对定位</code>的元素就是这样,它们处于正常的流程之外,放置在树中的其他地方,并映射到真正的框架,而放在原位的是<strong>占位框架</strong>。</div></blockquote><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_5bb8a55984c147bb8366b52d2aef30f1.png%22%2C%22originWidth%22%3A1610%2C%22originHeight%22%3A662%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A1610%2C%22height%22%3A662%7D"></span>主线程遍历Render Tree 然后生成Layout Tree (布局树)</div><div data-card-type="block" data-ready-card="hr"></div><h2 id="7ExlA"><br /></h2><h2 id="zLXkJ"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?target=" target="_blank">绘制(Paint)-生成元素绘制顺序</a></h2><div><br /></div><div>通过,布局处理,已经知晓所以元素的大小,位置。但是,还是不能进行<strong>按部就班</strong>的进行页面渲染,虽然HTML 采用<strong>基于流</strong>(<code>从左到右,从上到下</code>)的布局模型进行布局,但是通过样式,可以脱离了流的默认流向和渲染顺序。</div><div>例如我们可以通过<code>z-index</code>将在Z-轴方向搞点事情,这里就涉及到一个新的概念--层叠上下文 (这玩意也是一个很大的课题,如果有兴趣了解的话,还是可以参考张鑫旭大佬写的<a href="https://link.juejin.cn/?target=https%3A%2F%2Fwww.zhangxinxu.com%2Fwordpress%2F2016%2F01%2Funderstand-css-stacking-context-order-z-index%2F" target="_blank" ref="nofollow noopener noreferrer">深入理解CSS中的层叠上下文和层叠顺序</a> ) <span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_591309bab74b4492829ddd49d0d79343.png%22%2C%22originWidth%22%3A599%2C%22originHeight%22%3A375%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A599%2C%22height%22%3A375%7D"></span></div><div>直接上图,具体实现和讲解,就先不讨论了。</div><div>所以渲染器就从布局树的根节点进行遍历,按照<strong>各个维度</strong>进行最后的渲染顺序的确认,并生成元素的绘制顺序(paint record)。</div><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_3946b499c8184680b4bdde521272a21f.png%22%2C%22originWidth%22%3A1612%2C%22originHeight%22%3A658%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A1612%2C%22height%22%3A658%7D"></span></div><blockquote style="background-color: #F8F8F8;"><div>绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些<strong>待绘制列表</strong>。</div></blockquote><div data-card-type="block" data-ready-card="hr"></div><h2 id="qW2tS"><br /></h2><h2 id="HdN8r"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?spm=a2c6h.13046898.0.0.314e6ffaMwOelL&target=" target="_blank">页面合成(Compositing)-->先分层,栅格化->页面合成</a></h2><div><br /></div><div>页面合成是一种技术,将页面的各个部分分离成层,分别<strong>栅格化</strong>它们,并在称为<code>复合线程</code>的单独线程中复合为页面。如果发生滚动,因为图层已经栅格化了,它所要做的就是合成一个<strong>新帧</strong>。动画也可以通过移动图层和合成新帧来实现。</div><h3 id="8WlpP"><br /></h3><h3 id="a1V3t"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?target=" target="_blank">分层</a></h3><div><br /></div><div>现在我们已经知道了,元素之间的<strong>绘制顺序</strong>,此时如果一股脑的从根节点开始渲染,将会是一项很大的工程,所以渲染引擎为<strong>特定的节点</strong>生成专用的图层,并生成一棵对应的图层树(<code>LayerTree</code>)--> <strong>分而治之</strong></div><blockquote style="background-color: #F8F8F8;"><div>浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面</div><div><strong>分层的依据就是根据<code>层叠上下文</code>将页面分成不同的图层。</strong></div></blockquote><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_803db348c4144caaa0c561eb163bfd25.png%22%2C%22originWidth%22%3A1142%2C%22originHeight%22%3A674%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A1142%2C%22height%22%3A674%7D"></span></div><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_80c3fdc827f0471fb998dc8383bcc010.png%22%2C%22originWidth%22%3A1600%2C%22originHeight%22%3A660%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A1600%2C%22height%22%3A660%7D"></span></div><div>完成了图层树的分割,主线程就开始遍历图层,并且生成一系列<strong>渲染记录</strong> -> 用于指示渲染引擎对该图层的渲染顺序。</div><h3 id="7Ymrc"><br /></h3><h3 id="hRO5U"><a ref="nofollow noopener noreferrer" href="https://link.juejin.cn/?target=" target="_blank">栅格化(raster)操作</a></h3><div><br /></div><blockquote style="background-color: #F8F8F8;"><div>栅格化:将待绘制列表转换为屏幕中的像素</div></blockquote><div>绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的<code>合成线程</code>来完成的。</div><div>通常一个页面可能很大,但是用户只能看到其中的一部分,我们把用户可以看到的这个部分叫做视口(<code>viewport</code>)</div><div>在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。</div><div>基于这个原因,合成线程会将图层划分为<strong>图块(<code>tile</code>)</strong>,这些图块的大小通常是 <strong>256x256</strong> 或者 <strong>512x512</strong></div><blockquote style="background-color: #F8F8F8;"><div>合成线程会按照<strong>视口附近</strong>的图块来优先生成位图,实际生成位图的操作是由<code>栅格化</code>来执行的。所谓栅格化,是指将<code>图块转换为位图</code>。而<strong>图块是栅格化执行的最小单位</strong>。渲染进程维护了一个栅格化的<code>线程池</code>,所有的图块栅格化都是在线程池内执行的</div></blockquote><div><span data-card-type="inline" data-ready-card="image" data-card-value="data:%7B%22src%22%3A%22https%3A%2F%2Fucc.alicdn.com%2Fpic%2Fdeveloper-ecology%2Fzd7oztix3a4mm_373cc60389fe48e7a92251e2757c1fd8.png%22%2C%22originWidth%22%3A1142%2C%22originHeight%22%3A677%2C%22size%22%3A0%2C%22display%22%3A%22inline%22%2C%22align%22%3A%22left%22%2C%22linkTarget%22%3A%22_blank%22%2C%22status%22%3A%22done%22%2C%22style%22%3A%22none%22%2C%22search%22%3A%22%22%2C%22margin%22%3A%7B%22top%22%3Afalse%2C%22bottom%22%3Afalse%7D%2C%22width%22%3A1142%2C%22height%22%3A677%7D"></span></div><div>在处理完一帧的数据以后,就会向合成线程就会通过IPC将处理好的数据,返回给浏览器进程用于显示页面。而<strong>不占用渲染进程的主进程</strong>。</div><div>如此往复,直到合成线程中栅格化线程池中数据都被消费掉,页面也就渲染好了。</div><div data-card-type="block" data-ready-card="hr"></div><div>备注:该篇文章参考了很多资料,算是一个大杂烩。如果大家有兴趣,想看原文,可以直接参考。</div><ul><li><a href="https://link.juejin.cn/?target=https%3A%2F%2Fwww.html5rocks.com%2Fen%2Ftutorials%2Finternals%2Fhowbrowserswork%2F" target="_blank" ref="nofollow noopener noreferrer">How Browsers Work: Behind the scenes of modern web browsers</a></li><li><a href="https://link.juejin.cn/?target=https%3A%2F%2Fdevelopers.google.com%2Fweb%2Fupdates%2F2018%2F09%2Finside-browser-part1" target="_blank" ref="nofollow noopener noreferrer">Inside look at modern web browser </a></li><li><a href="https://link.juejin.cn/?target=https%3A%2F%2Fwww.w3.org%2FTR%2F2021%2FWD-css-cascade-5-20210119%2F" target="_blank" ref="nofollow noopener noreferrer">w3c</a></li><li>极客时间的课程</li></ul>

相关文章
|
18天前
|
移动开发 缓存 前端开发
深入理解前端路由:原理、实现与应用
本书《深入理解前端路由:原理、实现与应用》全面解析了前端路由的核心概念、工作原理及其实现方法,结合实际案例探讨了其在现代Web应用中的广泛应用,适合前端开发者和相关技术人员阅读。
|
1月前
|
前端开发 开发者
本文将深入探讨 BEM 的概念、原理以及其在前端开发中的应用
BEM(Block-Element-Modifier)是一种前端开发中的命名规范和架构方法,旨在提高代码的可维护性和复用性。通过将界面拆分为独立的模块,BEM 提供了一套清晰的命名规则,增强了代码的结构化和模块化设计,促进了团队协作。本文深入探讨了 BEM 的概念、原理及其在前端开发中的应用,分析了其优势与局限性,为开发者提供了宝贵的参考。
48 8
|
25天前
|
缓存 前端开发 JavaScript
JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式
本文深入解析了JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式(Hash路由和History路由)、优点及挑战,并通过实际案例分析,帮助开发者更好地理解和应用这一关键技术,提升用户体验。
63 1
|
1月前
|
监控 前端开发 jenkins
Jenkins 在前端项目持续部署中的应用,包括其原理、流程以及具体的实现方法
本文深入探讨了Jenkins在前端项目持续部署中的应用,涵盖其基本原理、流程及具体实现方法。首先介绍了Jenkins的基本概念及其在自动化任务中的作用,随后详细解析了从前端代码提交到生产环境部署的全过程,包括构建、测试、部署等关键步骤。最后,强调了持续部署中的代码质量控制、环境一致性、监控预警及安全管理等注意事项,旨在帮助开发者高效、安全地实施持续部署。
59 5
|
1月前
|
前端开发 JavaScript API
前端开发的秘密花园:这些技巧让你轻松应对各种浏览器兼容性问题!
【10月更文挑战第31天】前端开发是一个充满创意与挑战的领域,追求极致用户体验的同时,浏览器兼容性问题却时常阻碍我们前进。本文将介绍几种解决浏览器兼容性的最佳实践:使用CSS前缀、Autoprefixer工具、现代JavaScript特性与Babel转译、Polyfill与Feature Detection、响应式设计以及跨域问题处理。掌握这些技巧,助你轻松应对各种兼容性难题,创建更稳定、用户友好的网页应用。
33 3
|
1月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
111 1
|
1月前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
66 1
|
2月前
|
NoSQL 前端开发 MongoDB
前端的全栈之路Meteor篇(三):运行在浏览器端的NoSQL数据库副本-MiniMongo介绍及其前后端数据实时同步示例
MiniMongo 是 Meteor 框架中的客户端数据库组件,模拟了 MongoDB 的核心功能,允许前端开发者使用类似 MongoDB 的 API 进行数据操作。通过 Meteor 的数据同步机制,MiniMongo 与服务器端的 MongoDB 实现实时数据同步,确保数据一致性,支持发布/订阅模型和响应式数据源,适用于实时聊天、项目管理和协作工具等应用场景。
|
2月前
|
缓存 JavaScript 前端开发
拿下奇怪的前端报错(三):npm install卡住了一个钟- 从原理搞定安装的全链路问题
本文详细分析了 `npm install` 过程中可能出现的卡顿问题及解决方法,包括网络问题、Node.js 版本不兼容、缓存问题、权限问题、包冲突、过时的 npm 版本、系统资源不足和脚本问题等,并提供了相应的解决策略。同时,还介绍了开启全部日志、使用替代工具和使用 Docker 提供 Node 环境等其他处理方法。
1129 0
|
2月前
|
存储 安全 前端开发
在前端开发中需要考虑的常见web安全问题和攻击原理以及防范措施
在前端开发中需要考虑的常见web安全问题和攻击原理以及防范措施
218 0