天是 2020 年 4 月 4 日,星期六,清明节。
我们的国家经历了非常惨痛的时刻,很多英雄在救助他人的路上倒下,更有很多烈士英雄保卫人民的安危遇难,今天全国下降半旗,北京时间 10 点全国默哀三分钟,来致敬英雄们。同时今天一切公共娱乐活动也都会停止,包括直播、综艺、影视、游戏等等。
我在这里也向全国抗击新冠肺炎疫情斗争牺牲的烈士和逝世的同胞表达深切的哀悼,向所有抗战在疫情前线的工作和医护人员致敬。我们每一个人的平安面前,都是英雄的人墙。
今天大家可以看到很多很多网站包括主页和内容也都已经变成了灰色,比如百度、B 站、爱奇艺、CSDN 等等。
CSDN
爱奇艺
百度
大家可以看到全站的内容都变成灰色了,包括按钮、图片等等。这时候我们可能会好奇这是怎么做到的呢?
有人会以为所有的内容都统一换了一个 CSS 样式,图片也全换成灰色的了,按钮等样式也统一换成了灰色样式。但你想想这个成本也太高了,而且万一某个控件忘记加灰色样式了岂不是太突兀了。
其实,解决方案很简单,只需要几行代码就能搞定了。
我们选择一个网站,比如 B 站吧,打开浏览器开发者工具。
审查一下网页的源代码,我们可以发现在 html 的这个地方多了一个疑似的 class,叫做 gray,gray 中文即灰色。
变灰效果
其 CSS 内容为:
html.gray {
-webkit-filter: grayscale(.95);
}
我们将其取消,就能发现网站的颜色就能重新还原回来了。
还原效果
果然是这个样式在起作用,而且是全局的效果,因为它是作用在了 html 这个节点之上的。
另外看看 CSDN,它也是用的这个 CSS 样式,其内容为:
html {
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
filter: grayscale(100%);
filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
}
这个实现看起来兼容性会更好一些。
因此我们可以确定,通过一个全局的 CSS 样式就能将整个网站变成灰色效果。
那么这里我们就来详细了解一下这究竟是一个什么样的 CSS 样式。
这个样式名叫做 filter,搜下 MDN 的官方介绍,其链接为:https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter。
官方介绍内容如下:
filter CSS 属性将模糊或颜色偏移等图形效果应用于元素。滤镜通常用于调整图像,背景和边框的渲染。
CSS 标准里包含了一些已实现预定义效果的函数。你也可以参考一个 SVG 滤镜,通过一个 URL 链接到 SVG 滤镜元素 (SVG filter element[1])。
其实就是一个滤镜的意思。
官方有一个 Demo,可以看下效果,如图所示。
Demo
比如这里通过 filter 样式改变了图片、颜色、模糊、对比度等等信息。
其所有用法示例如下:
/* URL to SVG filter */
filter: url("filters.svg#filter-id");
/* <filter-function> values */
filter: blur(5px);
filter: brightness(0.4);
filter: contrast(200%);
filter: drop-shadow(16px 16px 20px blue);
filter: grayscale(50%);
filter: hue-rotate(90deg);
filter: invert(75%);
filter: opacity(25%);
filter: saturate(30%);
filter: sepia(60%);
/* Multiple filters */
filter: contrast(175%) brightness(3%);
/* Global values */
filter: inherit;
filter: initial;
filter: unset;
各个用法介绍大家可以参考官方的文档说明:https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter
比如这里如果我们可以使用 blur 设置高斯模糊,用法如下:
filter: blur(radius)
给图像设置高斯模糊。radius 一值设定高斯函数的标准差,或者是屏幕上以多少像素融在一起,所以值越大越模糊;如果没有设定值,则默认是 0;这个参数可设置绝对像素值,但不接受百分比值。
可以达成这样的效果:
效果
再说回刚才的灰色图像,这里其实就是设置了 grayscale,其用法如下:
filter: grayscale(percent)
将图像转换为灰度图像。值定义转换的比例。percent 值为 100% 则完全转为灰度图像,值为 0% 图像无变化。值在 0% 到 100% 之间,则是效果的线性乘子。若未设置,值默认是 0。另外除了传递百分比,还可以传递浮点数,效果是一样的。
如:
filter: grayscale(1)filter: grayscale(100%)
都可以将节点转化为 100% 的灰度模式。
所以一切到这里就清楚了,如果我们想要把全站变成灰色,再考虑到各浏览器兼容写法,可以参考下 CSDN 的写法:
.gray {
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
filter: grayscale(100%);
filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
}
这样想要变灰的节点只需要加上 gray 这个 class 就好了,比如加到 html 节点上就可以全站变灰了。
最后呢,看一下浏览器对 filter 这个样式的兼容性怎样,如图所示:
兼容性
这里我们看到,这里除了 IE,其他的 PC、手机端的浏览器都支持了,Firefox 的 PC、安卓端还单独对 SVG 图像加了支持,可以放心使用。
本篇文章简单介绍了一下今天观察到的网站变灰的实现,也学习了 filter 的更详细的用法,希望有帮助。
[1] SVG filter element: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter
文最初发表于HTTP Toolkit网站,经原作者 Tim Perry 授权由 InfoQ 中文站翻译分享。
万物都会有终结,HTTP API 也不例外。不论你的 API 今天看上去多么伟大,迟早有一天你会想发布一个全新的版本,新版本能更好地解决相同问题,在各方面可能都会有所改善,但是它因为有了新参数,与旧版本也无法兼容,或者你只是想彻底关闭旧的 API。总而言之,你现在的 API 不会永远存在。
但是,这并非轻而易举就能完成的,因为你的 API 有客户端。如果你关闭端点、参数或整个 API 而没有做出恰当的警告的话,那他们肯定会非常不爽。
那么,该怎样安全地关闭 API,让你的用户尽可能地感到轻松愉快呢?
在这方面,我们有正确的做事方式,包括两个新的头信息草案,它们正在被新的 IETF “Building Blocks for HTTP APIs”工作组进行标准化,旨在形成一个精确的过程。我们了解一下。
初始第一步:检查相关的 API 是否真的有客户端。
希望你能有某些 API 的度量指标,至少在某些地方存有日志。如果没有的话,那把它们添加上,如果你有这些东西的话,并且你能确定没有人再使用这个 API 了,那么恭喜你,你赢了。现在,你就可以把它关掉,删掉代码,不要再管这篇文章了,好好睡一觉。
下一个问题,如果比较遗憾,你无法去睡觉的话,那就要问问自己,除了关闭这个 API,还有没有其他方案。你关闭的所有东西都有可能破坏别人的代码,并且会消耗他们的时间来修复这些问题。如果 API 能继续运行的话,对客户端的生态系统和整个 web 的健康都是有好处的。
在很多场景下,旧的 API 可以在内部进行转换,透明地转化成对新 API 的调用,这样可以避免维护两个完全独立的版本。这是Stripe的API版本管理方式的一个基本组成部分,他们在所有发生变化的 API 中都包含了转换,以确保对不兼容的旧版本 API 的请求能继续像以前那样运行,根据需要自动转换请求和响应从而可以使用较新的代码。
这样的转换并不总是可行的,而且如果永远这样做的话会带来明显的额外复杂性,但是如果你可以做到这一点的话,就能为用户提供非常有价值的稳定性,并且可以节省大量废弃旧版本或维护旧版本相关的工作。
但是,如果这个服务/端点/参数已经用到了生产环境,而且继续支持它是不现实的做法,那么它必须要被淘汰。
要实现这一点,我们就要有一个计划。我们首先要问自己三个关键的问题:
计划准备就绪之后,我们就该把它告诉人们了。
首先,要把这一决定告诉人们。
发邮件到邮件列表,在 Twitter 或微博上发帖,如果有 API 规范的话,对其进行更新(比如,OpenAPI 在operations和parameters上有一个deprecated字段),并在相关的在线文档上大声强调这一点。
你应该包含上文提到的所有信息:他们应该做些什么作为替代方案,你建议他们什么时候开始迁移以及他们必须要进行迁移的最后期限(如果已经确定期限的话)。
在将这些信息告诉给人们后,接下来就该告诉计算机,而这就是新的 IETF 头信息可以发挥作用的地方。
Deprecation头信息能告诉客户端请求的资源现在依然像以前那样运行,但是这种方式已经不再推荐使用了。通过一个简单的 HTTP 头信息,我们就可以声明这一点:
Deprecation: true
另外,我们还能提供一个日期。这个日期告诉用户他们何时该开始进行迁移。这个日期可以是一个过去时间(这代表他们应该立即开始迁移),也能是将来时间(通常这意味着他们要迁移到的新环境还没有准备就绪)。如下所示:
Deprecation: Thu, 21 Jan 2021 23:59:59 GMT
如果你要废弃整个端点或服务,那么你可以在每个响应中都带上这样的头信息。如果你想要废弃的是一个具体的特性,可能是一个参数、请求方法或者请求体中的某个特定字段的话,那么你应该在该特性被使用的时候才在响应中包含这个头信息。
为了给客户端更多的信息,我们还可以使用Link HTTP 响应头信息链接至端点或人类易读的文档。在同一个Link头信息中,我们可以包含多个这样的链接,只需要使用逗号进行分割即可(后面我们会看到一个完整的例子)。该规范定义了四个与 API 废弃相关的链接:
我们可以为 deprecation 链接指向一个人类易于阅读的描述:
Link: <https://developer.example.com/deprecation>; rel="deprecation"; type="text/html"
这是告诉用户发生了什么以及他们该怎么办的主要方式。你应该始终使用它。如果还没有完整的详情和最终的关闭日期,那么即使只是一个占位符,这也是很有帮助的。在这种情况下,不要忘记让用户订阅更新,这可以采用邮件列表、RSS 或其他类似的方式来实现。
如果你希望客户端转移至 API 相同端点的最新版本,那么可以使用该链接指向它,如下所示:
Link: <https://api.example.com/v10/customers>; rel="latest-version"
如果你的 API 有多个可用的版本,通常最好每次向前迁移一个版本,而不是直接从最老的、现已废弃的版本跳到最新的版本。为了帮助解决这个问题,我们链接至已废弃版本的下一个版本,而不是最新版本,如下所示:
Link: <https://api.example.com/v2/customers>; rel="successor-version"
如果该 API 没有新的等价版本,用户最好迁移到一个完全不同的资源,它可能是一个很好的替代方案,那么我们使用 alternate 链接来指明这一点,如下所示:
Link: <https://api.example.com/v2/users/123/clients>; rel="alternate"
如果你知道了 API 何时完全关闭的话,那么就应该添加一个Sunset头信息。
Sunset 头信息告诉客户端 API 何时会停止运行。这是一个强制的截止时间:API 客户端必须要在这个日期前进行迁移,我们承诺在这个时间前不会破坏任何事情。
在这里,我们必须要提供一个时间,它应该是一个未来的时间。不过,如果它是一个过去的时间,这也是可以的:此时就相当于说“这个 API 会在任意时刻关闭,你需要立即停止使用它”。它如下所示:
Sunset: Tue, 20 Jul 2021 23:59:59 GMT
这非常简单,它不仅可以用到 API 关闭的场景中:我们能用它来标记将来 URL 迁移的 HTTP 重定向,或者表明特定 URL 有限的生命周期(适用于临时性的内容,或者适用于具有监管要求的特定资源,比如数据保留策略)。它所说明的就是“这个端点可能在该日期后不会再按照你的预期运行,请做好准备”。
该规范也提供了一个 Sunset 链接的关系。按照设计,它会链接至关于关闭特定端点更加详细的信息(如果你有 deprecation 链接的话,它们可能会是同一个)或者关于服务的通用 Sunset 策略。如下所示:
Link: <http://developer.example.com/our-sunset-policy>;rel="sunset";type="text/html"
在这里我们也要指出,通用的 Sunset 策略是非常有用的!Sunset 策略会告诉客户端,当我们关闭端点的时候(比如,一年后替代方案上线),用户该如何确保他们能得知这一情况(邮件列表、状态页面、HTTP 头信息等)以及他们通常应该做些什么(更新、检查文档、遵从Link头信息)。
如果马上就要废弃某个 API 的话,添加这样的链接作用其实不大,但是如果你能在一年前就将其发布出去的话,你的客户端可能已经为此做好了准备。
除此之外,发布 Sunset/Deprecation 策略的最好时间就是现在。如果你恰好正以某种方式编写 Deprecation 文档的话,这么做是值得考虑的。
按照设计,这些组成部分能很好地协作。例如,为了表明某个最近废弃的 API,该 API 会在 6 个月内彻底关闭,我们要链接至文档并提供下一个版本的直接链接,那么我们应该在响应中包含如下的头信息链接:
Deprecation: Thu, 21 Jan 2021 23:59:59 GMT
Sunset: Tue, 20 Jul 2021 23:59:59 GMT
Link: <https://api.example.com/v2/customers>; rel="successor-version",
<https://developer.example.com/shutting-down-customers-v1>; rel="deprecation"
如果所有这些都已经准备到位,并且 sunset 截止时间已过,那么我们就可以将 API 关闭了。
但是,这并不意味着你需要立即且彻底消灭该 API。渐进式关闭能有助于确保任何使用该 API 的所有客户端都有最后的机会在它彻底消失前得到最后一次警告。GitHub 在 2018 年移除一些加密支持的时候曾经这样做:首先禁用一个小时,然后启用它,最后在两周后彻底禁用了它。
这里还有另外一个技巧:安卓在 2015 年为已废弃的原生API增加了越来越多的延迟,在彻底关闭 API 前,最终达到了 16 秒的等待。
这些渐进式的关闭为那些错过截止日期的客户端提供了一些灵活性,并且能帮助那些没有注意到废弃时间点的客户端,从而能在 API 彻底关闭之前处理一些问题。
不管采用哪种方式,只要你尽了最大的努力去沟通关于 API 关闭的事情,那么现在就可以关闭端点/特性/整个服务,删除代码,然后睡个好觉。
像这样小心谨慎地进行废弃和关闭,可以让你的客户端尽可能清楚地知道他们该如何依赖你的 API,何时需要采取行动,以及他们需要做什么。这种变更可能是一件大事儿,这些信息是很重要的。
这些新的草案头信息让我们不仅可以与人类沟通,还能将这些信息暴露给自动化系统。随着这些头信息的普及,我很高兴地开始看到有更多的工具建立在它们之上。通用的 HTTP 客户端可以根据这些数据自动记录有用的警告日志,API 生成器本身也能根据 API 规范处理越来越多的问题,而 HTTP 调试器(如HTTP Toolkit)可以在截获的实时流量中为你突出显示废弃端点的使用。这是一个令人激动的时刻,我们可以开始安全地关闭 API 了!
需要注意的是,这些头信息是 HTTP 规范的草案。在最终确定前,它们有可能会发生变化。也就是说,它们经历了几轮修改,从现在开始,它们不太可能发生巨大的变化,现在能广泛测试它们了。
不过这也意味着还有时间进行反馈! 如果你对它们的工作方式和如何更好地运行有想法的话,请与“Building Blocks for HTTP APIs”工作组联系。你可以给邮件列表发邮件:httpapi@ietf.org,或者在这里查看之前的邮件列表讨论。
原文链接:
https://httptoolkit.tech/blog/how-to-turn-off-your-old-apis/
浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分的,一是渲染引擎,另一个是 JS 引擎。渲染引擎在不同的浏览器中也不是都相同的。目前市面上常见的浏览器内核可以分为这四种:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。这里面大家最耳熟能详的可能就是 Webkit 内核了,Webkit 内核是当下浏览器世界真正的霸主。
本文我们就以 Webkit 为例,对现代浏览器的渲染过程进行一个深度的剖析。
想阅读更多优质文章请猛戳GitHub 博客。
在介绍浏览器渲染过程之前,我们简明扼要介绍下页面的加载过程,有助于更好理解后续渲染过程。
要点如下:
例如在浏览器输入https://juejin.im/timeline,然后经过 DNS 解析,juejin.im对应的 IP 是36.248.217.149(不同时间、地点对应的 IP 可能会不同)。然后浏览器向该 IP 发送 HTTP 请求。
服务端接收到 HTTP 请求,然后经过计算(向不同的用户推送不同的内容),返回 HTTP 请求,返回的内容如下:
其实就是一堆 HMTL 格式的字符串,因为只有 HTML 格式浏览器才能正确解析,这是 W3C 标准的要求。接下来就是浏览器的渲染过程。
浏览器渲染过程大体分为如下三部分:
1)浏览器会解析三个东西:
一是 HTML/SVG/XHTML,HTML 字符串描述了一个页面的结构,浏览器会把 HTML 结构字符串解析转换 DOM 树形结构。
二是 CSS,解析 CSS 会产生 CSS 规则树,它和 DOM 结构比较像。
三是 Javascript 脚本,等到 Javascript 脚本文件加载后, 通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。
2)解析完成后,浏览器引擎会通过 DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。
3)最后通过调用操作系统 Native GUI 的 API 绘制。
接下来我们针对这其中所经历的重要步骤详细阐述
浏览器会遵守一套步骤将 HTML 文件转换为 DOM 树。宏观上,可以分为几个步骤:
浏览器从磁盘或网络读取 HTML 的原始字节,并根据文件的指定编码(例如 UTF-8)将它们转换成字符串。
在网络中传输的内容其实都是 0 和 1 这些字节数据。当浏览器接收到这些字节数据以后,它会将这些字节数据转换为字符串,也就是我们写的代码。
将字符串转换成 Token,例如:<html>、<body>等。Token 中会标识出当前 Token 是“开始标签”或是“结束标签”亦或是“文本”等信息。
这时候你一定会有疑问,节点与节点之间的关系如何维护?
事实上,这就是 Token 要标识“起始标签”和“结束标签”等标识的作用。例如“title”Token 的起始标签和结束标签之间的节点肯定是属于“head”的子节点。
上图给出了节点之间的关系,例如:“Hello”Token 位于“title”开始标签与“title”结束标签之间,表明“Hello”Token 是“title”Token 的子节点。同理“title”Token 是“head”Token 的子节点。
事实上,构建 DOM 的过程中,不是等所有 Token 都转换完成后再去生成节点对象,而是一边生成 Token 一边消耗 Token 来生成节点对象。换句话说,每个 Token 被生成后,会立刻消耗这个 Token 创建出节点对象。注意:带有结束标签标识的 Token 不会创建节点对象。
接下来我们举个例子,假设有段 HTML 文本:
复制代码
<html> <head> <title>Web page parsing</title> </head> <body> <div> <h1>Web page parsing</h1> <p>This is an example Web page.</p> </div> </body> </html>
上面这段 HTML 会解析成这样:
DOM 会捕获页面的内容,但浏览器还需要知道页面如何展示,所以需要构建 CSSOM。
构建 CSSOM 的过程与构建 DOM 的过程非常相似,当浏览器接收到一段 CSS,浏览器首先要做的是识别出 Token,然后构建节点并生成 CSSOM。
在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。
注意:CSS 匹配 HTML 元素是一个相当复杂和有性能问题的事情。所以,DOM 树要小,CSS 尽量用 id 和 class,千万不要过渡层叠下去。
当我们生成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。
在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 display: none 的,那么就不会在渲染树中显示。
我们或许有个疑惑:浏览器如果渲染过程中遇到 JS 文件怎么处理?
渲染过程中,如果遇到<script>就停止渲染,执行 JS 代码。因为浏览器有 GUI 渲染线程与 JS 引擎线程,为了防止渲染出现不可预期的结果,这两个线程是互斥的关系。JavaScript 的加载、解析与执行会阻塞 DOM 的构建,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停构建 DOM,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复 DOM 构建。
也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性(下文会介绍这两者的区别)。
JS 文件不只是阻塞 DOM 的构建,它会导致 CSSOM 也阻塞 DOM 的构建。
原本 DOM 和 CSSOM 的构建是互不影响,井水不犯河水,但是一旦引入了 JavaScript,CSSOM 也开始阻塞 DOM 的构建,只有 CSSOM 构建完毕后,DOM 再恢复 DOM 构建。
这是什么情况?
这是因为 JavaScript 不只是可以改 DOM,它还可以更改样式,也就是它可以更改 CSSOM。因为不完整的 CSSOM 是无法使用的,如果 JavaScript 想访问 CSSOM 并更改它,那么在执行 JavaScript 时,必须要能拿到完整的 CSSOM。所以就导致了一个现象,如果浏览器尚未完成 CSSOM 的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和 DOM 构建,直至其完成 CSSOM 的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建 CSSOM,然后再执行 JavaScript,最后在继续构建 DOM。
当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。
布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。
布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。
以上我们详细介绍了浏览器工作流程中的重要步骤,接下来我们讨论几个相关的问题:
1.async 和 defer 的作用是什么?有什么区别?
接下来我们对比下 defer 和 async 属性的区别:
其中蓝色线代表 JavaScript 加载;红色线代表 JavaScript 执行;绿色线代表 HTML 解析。
1)情况 1<script src="script.js"></script>
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。
2)情况 2<script async src="script.js"></script> (异步下载)
async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。
3)情况 3 <script defer src="script.js"></script>(延迟执行)
defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。
defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。
在加载多个 JS 脚本的时候,async 是无顺序的加载,而 defer 是有顺序的加载。
2. 为什么操作 DOM 慢?
把 DOM 和 JavaScript 各自想象成一个岛屿,它们之间用收费桥梁连接。——《高性能 JavaScript》
JS 是很快的,在 JS 中修改 DOM 对象也是很快的。在 JS 的世界里,一切是简单的、迅速的。但 DOM 操作并非 JS 一个人的独舞,而是两个模块之间的协作。
因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们用 JS 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了“跨界交流”。这个“跨界交流”的实现并不简单,它依赖了桥接接口作为“桥梁”(如下图)。
过“桥”要收费——这个开销本身就是不可忽略的。我们每操作一次 DOM(不管是为了修改还是仅仅为了访问其值),都要过一次“桥”。过“桥”的次数一多,就会产生比较明显的性能问题。因此“减少 DOM 操作”的建议,并非空穴来风。
3. 你真的了解回流和重绘吗?
渲染的流程基本上是这样(如下图黄色的四个步骤):
1. 计算 CSS 样式
2. 构建 Render Tree
3.Layout – 定位坐标和大小
4. 正式开画
注意:上图流程中有很多连接线,这表示了 Javascript 动态修改了 DOM 属性或是 CSS 属性会导致重新 Layout,但有些改变不会重新 Layout,就是上图中那些指到天上的箭头,比如修改后的 CSS rule 没有被匹配到元素。
这里重要要说两个概念,一个是 Reflow,另一个是 Repaint
重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。
回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来,这个过程就是回流(也叫重排)。
我们知道,当网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断重新渲染。重新渲染会重复回流 + 重绘或者只有重绘。
回流必定会发生重绘,重绘不一定会引发回流。重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
1)常见引起回流属性和方法
任何会改变元素几何信息 (元素的位置和尺寸大小) 的操作,都会触发回流,
2)常见引起重绘属性和方法
3)如何减少回流、重绘
复制代码
for(let i = 0; i < 1000; i++) { // 获取 offsetTop 会导致回流,因为需要去获取正确的值 console.log(document.querySelector('.test').style.offsetTop) }
基于上面介绍的浏览器渲染原理,DOM 和 CSSOM 结构构建顺序,初始化可以对页面渲染做些优化,提升页面性能。
综上所述,我们得出这样的结论:
参考文章
更多内容,请关注前端之巅。
*请认真填写需求信息,我们会在24小时内与您取得联系。