浏览器相关

浏览器组成

浏览器主要由7个部分组成:

  • 用户界面(User Interface):定义了一些常用的浏览器组件,比如地址栏,返回、书签等等
  • 数据持久化(Data Persistence):指浏览器的cookie、local storage等组件
  • 浏览器引擎(Browser engine):平台应用的相关接口,在用户界面和呈现引擎之间传送指令。
  • 渲染引擎(Rendering engine):处理HTML、CSS的解析与渲染
  • JavaScript解释器(JavaScript Interpreter):解析和执行JavaScript代码
  • 用户界面后端(UI Backend):指浏览器的的图形库等
  • 网络(Networking):用于网络调用,比如HTTP请求

浏览器内核

浏览器内核分为两部分:渲染引擎(layout engineer或Rendering Engine)和JS引擎

  • 渲染引擎:负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机
  • JS引擎:负责解析和执行javascript来实现网页的动态效果
    浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核,最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎

浏览器内核

  • Trident内核:IE,MaxThon,TT,The Word,360,搜狗浏览器等。[又称为MSHTML]
  • Gecko内核:Netscape6及以上版本,FF,MozillaSuite/SeaMonkey等;
  • Presto内核:Opera7及以上。[Opera内核原为:Presto,现为:Blink]
  • Webkit内核:Safari,Chrome等。[Chrome的:Blink(Webkit的分支)]
  • 对于Android手机而言,使用率最高的就是Webkit内核。

常见的浏览器内核:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)

浏览器页面加载流程

在了解浏览器渲染过程之前,先来了解一下页面的加载流程。有助于更好理解后续渲染过程。从浏览器地址中从输入url地址到渲染出一个页面,会经过以下过程。
1.浏览器输入的url地址经过DNS解析获得对应的IP
2.向服务器发起TCP的3次握手
3.建立链接后,浏览器向该IP地址发送http请求
4.服务器接收到请求,返回一堆 HMTL 格式的字符串代码
5.浏览器获得html代码,解析成DOM树
6.获取CSS并构建CSSOM
7.将DOM与CSSOM结合,创建渲染树
8.找到所有内容都处于网页的哪个位置,布局渲染树
9.最终绘制出页面

浏览器渲染机制

我们将要介绍的浏览器渲染过程主要步骤是5-9步,可以用下面的图来形象的展示

解析HTML成DOM树

这个解析过程大概可以分为几个步骤:

第一步:浏览器从磁盘或网络读取HTML的原始字节,也就是传输的0和1这样的字节数据,并根据文件的指定编码(例如 UTF-8)将它们转换成字符串。
第二步:将字符串转换成Token,例如:“”、“”等。Token中会标识出当前Token是“开始标签”或是“结束标签”亦或是“文本”等信息
第三步:在每个Token被生成后,会立刻消耗这个Token创建出节点对象,因此在构建DOM的过程中,不是等待所有的Token都生成后才去构建DOM,而是一边生成Token一边消耗来生成节点对象。

注意:带有结束标签标识的Token不会创建节点对象 第四步:通过“开始标签”与“结束标签”来识别并关联节点之间的关系。当所有Token都生成并消耗完毕后,我们就得到了一颗完整的DOM树。

但是现在有一个疑问,节点之间的关联关系是如何维护的呢?
上面我们提到Token会标识是“开始标签”还是“结束标签”,以下图为例:“Hello”Token位于“title”开始标签与“title”结束标签之间,表明“Hello”Token是“title”Token的子节点。同理“title”Token是“head”Token的子节点。

构建CSSOM

既然有了html解析,那css解析也是必不可少的,解析css构建CSSOM 的过程和构建DOM的过程非常的相似。当浏览器接收到一段CSS,浏览器首先要做的是识别出Token,然后构建节点并生成CSSOM

节点中样式可以通过继承得到,也可以自己设置,因此在构建的过程中浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。为了CSSOM的完整性,也只有等构建完毕才能进入到下一个阶段,哪怕DOM已经构建完,它也得等CSSOM,然后才能进入下一个阶段。

CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去 所以,CSS的加载速度与构建CSSOM的速度将直接影响首屏渲染速度,因此在默认情况下CSS被视为阻塞渲染的资源

构建渲染树

当我们生成DOM树和CSSOM树后,我们需要将这两颗树合并成渲染树,在构建渲染树的过程中浏览器需要做如下工作:

  • 从 DOM 树的根节点开始遍历每个可见节点。
  • 有些节点不可见(例如脚本Token、元Token等),因为它们不会体现在渲染输出中,所以会被忽略。
  • 某些节点被CSS隐藏,因此在渲染树中也会被忽略。例如某些节点设置了display: none属性。
  • 对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们

渲染阻塞

在渲染的过程中,遇到一个script标记时,就会停止渲染,去请求脚本文件并执行脚本文件,因为浏览器渲染和 JS 执行共用一个线程,而且这里必须是单线程操作,多线程会产生渲染 DOM 冲突。JavaScript的加载、解析与执行会严重阻塞DOM的构建。只有等到脚本文件执行完毕,才会去继续构建DOM。

js不单会阻塞DOM构建,还会导致CSSOM也阻塞DOM的构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM还没有下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和构建,然后再执行JavaScript,最后在继续构建DOM

因此script的位置很重要,在实际使用过程中遵循以下两个原则:

  • CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。
  • JS置后:我们通常把JS代码放到页面底部,且JavaScript 应尽量少影响 DOM 的构建

布局与绘制

浏览器拿到渲染树后,就会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,通常这一行为也被称为“自动重排”。布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小,所有相对测量值都将转换为屏幕上的绝对像素。这一过程也可称为回流

布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。

性能优化策略

在我们了解浏览器的渲染机制后,DOM 和 CSSOM 结构构建顺序,我们可以针对性能优化问题给出一些方案,提升页面性能。

1.回流(reflow)与重绘(repaint)

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。

  • 重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此损耗较少
  • 回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:
  • 添加或删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  • 页面一开始渲染的时候(这肯定避免不了)
  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的

注意:回流一定会触发重绘,而重绘不一定会回流,重绘的开销较小,回流的代价较高

因此为了减少性能优化,我们可以尽量避免回流或者重绘操作
css

  • 避免使用table布局
  • 将动画效果应用到position属性为absolute或fixed的元素上

javascript

  • 避免频繁操作样式,可汇总后统一 一次修改
  • 尽量使用class进行样式修改
  • 减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入
  • 极限优化时,修改样式可将其display: none后修改
  • 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住

async和defer的作用是什么?有什么区别?

defer 和 async 属性的区别:

其中蓝色线代表JavaScript加载;红色线代表JavaScript执行;绿色线代表 HTML 解析
1)情况1 <scriptsrc="script.js">
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。

2)情况2  (异步下载)
async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。

3)情况3 <scriptdefersrc="script.js">(延迟执行)
defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。

defer 与相比普通 script,有两点区别:

  • 载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后;
  • 在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载

js优化可以在script 标签加上 defer属性 和 async属性用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行

其他: CSS 标签的 rel属性 中的属性值设置为 preload 能够让你在你的HTML页面中可以指明哪些资源是在页面加载完成后即刻需要的,最优的配置加载顺序,提高渲染性能

首屏优化加载

  • 减少首屏CGI的计算量:比如在微信8.8无现金日H5开发中,前端希望拿到用户的个人信息、消费记录、排名三类数据,如果只通过一个CGI来处理,那么后台响应时间肯定会变长;由于在H5的首屏中,只包含了用户信息,消费记录、排名都在第2屏和第3屏,此时其实可以利用异步的方式来拿消费记录、排名的数据。
  • 页面瘦身:压缩HTML、CSS、JavaScript。
  • 减少请求:CSS、JavaScript文件数尽量少,甚至当CSS、JS的代码不多时,可以考虑直接将代码内嵌到页面中。
  • 多用缓存:缓存能大幅度降低页面非首次加载的时间。
  • 少用table布局,浏览器在渲染table时会消耗较多资源,而且只有table里有一点变化,整个table都会重新渲染。
  • 做预加载:部分H5页面首屏可能要下载较多的静态资源,比如图片,这时为了避免加载时出现“难看”的页面,用预加载(loading的方式)做一个过渡

总结
我们已经将浏览器的渲染机制了解了一遍,不仅了解到一些性能优化方案,也可以得出结论:
浏览器渲染的关键路径共分五个步骤:

构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制

浏览器的缓存机制

浏览器缓存机制有两种,一种为强缓存,一种为协商缓存。
对于强缓存,浏览器在第一次请求的时候,会直接下载资源,然后缓存在本地,第二次请求的时候,直接使用缓存。
对于协商缓存,第一次请求缓存且保存缓存标识与时间,重复请求向服务器发送缓存标识和最后缓存时间,服务端进行校验,如果失效则使用缓存。
强缓存方案
Exprires:服务端的响应头,第一次请求的时候,告诉客户端,该资源什么时候会过期。Exprires的缺陷是必须保证服务端时间和客户端时间严格同步。
Cache-control:max-age,表示该资源多少时间后过期,解决了客户端和服务端时间必须同步的问题,
协商缓存方案
If-None-Match/ETag:缓存标识,对比缓存时使用它来标识一个缓存,第一次请求的时候,服务端会返回该标识给客户端,客户端在第二次请求的时候会带上该标识与服务端进行对比并返回If-None-Match标识是否表示匹配。
Last-modified/If-Modified-Since:第一次请求的时候服务端返回Last-modified表明请求的资源上次的修改时间,第二次请求的时候客户端带上请求头If-Modified-Since,表示资源上次的修改时间,服务端拿到这两个字段进行对比

ETag如何生成


在REST架构下,ETag值可以通过Guid、整数、长整形、字符串四种类型的参数传入SetETag方法,
WCF服务发回给客户端的HTTP响应头中就包含了ETag值。另外OutgoingResponse类也有字符串属性:ETag直接给它赋值也能在HTTP响应头中写入ETag值
计算ETag值时,需要考虑两个问题:计算与存储。如果一个ETag值只需要很小的代价以及占用很低的存储空间,那么我们可以在每次需要发送给客户端ETag值值的时候计算一遍就行行了。相反的,我们需要将之前就已经计算并存储好的ETag值发送给客户端。之前说:将时间戳作为字符串作为一种廉价的方式来获取ETag值。对于不是经常变化的消息,它是一种足够好的方案。注意:如果将时间戳做为ETag值,通常不应该用Last-Modified的值。由于HTTP机制中,所以当我们在通过服务校验资源状态时,客户端不需要进行相应的改动。计算ETag值开销最大的一般是计算采用哈希算法
获取资源的表述值。可以只计算资源的哈希值,也可以将头信息和头信息的值也包含进去。如果包含头信息,那么注意不要包含计算机标识的头信息。同样也应该避免包含Expires、Cache-Control和Vary头信息。注意:在通过哈希算法计算ETag值时,先要组装资源的表述。若组装也比较耗时,可以采用生成GUID的方式。优化ETag值的获取
ETag有两种类型:强ETag(strong ETag)与弱ETag(weak ETag)。

  • 强ETag表示形式:"22FAA065-2664-4197-9C5E-C92EA03D0A16"。
  • 弱ETag表现形式:w/"22FAA065-2664-4197-9C5E-C92EA03D0A16"。
    强、弱ETag类型的出现与Apache服务器计算ETag的方式有关。Apache默认通过FileEtag中FileEtag INode Mtime Size的配置自动生成ETag(当然也可以通过用户自定义的方式)。假设服务端的资源频繁被修改(如1秒内修改了N次),此时如果有用户将Apache的配置改为MTime,由于MTime只能精确到秒,那么就可以避免强ETag在1秒内的ETag总是不同而频繁刷新Cache(如果资源在秒级经常被修改,也可以通过Last-Modified来解决)

浏览器缓存

  • 缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。
  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

1.Service Worker
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据.
当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
2.Memory Cache
Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢? 这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。 当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存 内存缓存中有一块重要的缓存资源是preloader相关指令(例如)下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。 需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验

3.Disk Cache
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍。
浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?
关于这点,网上说法不一,不过以下观点比较靠得住:

  • 对于大文件来说,大概率是不存储在内存中的,反之优先
  • 当前系统内存使用率高的话,文件优先存储进硬盘

4.Push Cache
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。

  • 所有的资源都能被推送,并且能够被缓存,但是 Edge 和 Safari 浏览器支持相对比较差
  • 可以推送 no-cache 和 no-store 的资源
  • 一旦连接被关闭,Push Cache 就被释放
  • 多个页面可以使用同一个HTTP/2的连接,也就可以使用同一个Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。
  • Push Cache 中的缓存只能被使用一次
  • 浏览器可以拒绝接受已经存在的资源推送
  • 你可以给其他域名推送资源

如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。
那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的

web worker

现代浏览器为JavaScript创造的 多线程环境。可以新建并将部分任务分配到worker线程并行运行,两个线程可 独立运行,互不干扰,可通过自带的 消息机制 相互通信
用法:

const worker = new Worker('work.js');
// 向主进程推送消息
worker.postMessage('Hello World');
// 监听主进程来的消息
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
}
1
2
3
4
5
6
7

限制:
同源限制
无法使用 document / window / alert / confirm
无法加载本地资源

同源策略

同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议
举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
它的核心就在于它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。
所谓同源是指:域名、协议、端口相同。
同源策略又分为以下两种:

  • DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
  • XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。

没有同源策略会怎样:

  • 如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:
  • 如果 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:

扩展:跨域第方式有哪些

  • CORS(跨域资源共享)
  • JSONP 跨域
  • 图像 Ping 跨域
  • 服务器代理
  • document.domain 跨域
  • window.name 跨域
  • location.hash 跨域
  • postMessage 跨域

### **浏览器内跨标签的通信** 不同标签页间的通讯,本质原理就是去运用一些可以 共享的中间介质,因此比较常用的有以下方法:
通过父页面window.open()和子页面postMessage
  • 异步下,通过 window.open('about: blank') 和 tab.location.href = '*'

设置同域下共享的localStorage与监听window.onstorage

  • 重复写入相同的值无法触发
  • 会受到浏览器隐身模式等的限制

设置共享cookie与不断轮询脏检查(setInterval)
借助服务端或者中间层实现

  • WebSocket、SharedWorker;
  • 也可以调用 localstorge、cookies 等本地存储方式;
  • localstorge 在另一个浏览上下文里被添加、修改或删除时,它都会触发一个事件,我们通过监听事件,控制它的值来进行页面信息通信; 注意 quirks:Safari 在无痕模式下设置 localstorge 值时会抛出 QuotaExceededError 的异常;

浏览器和 Node 事件循环的区别


主要的区别在于浏览器的event loop 和nodejs的event loop 在处理异步事件的顺序是不同的,nodejs中有micro event;其中Promise属于micro event 该异步事件的处理顺序就和浏览器不同.nodejs V11.0以上 这两者之间的顺序就相同了.
浏览器下事件循环(Event Loop):
微任务 microtask(jobs): promise / ajax / Object.observe(该方法已废弃)
宏任务 macrotask(task): setTimout / script / IO / UI Rendering
Node 的 Event Loop

  • timer 阶段: 执行到期的setTimeout / setInterval队列回调
  • I/O 阶段: 执行上轮循环残流的callback
  • idle, prepare
  • poll: 等待回调
    • 执行回调
    • 执行定时器
      • 到期的setTimeout / setInterval, 则返回 timer 阶段
      • 如有setImmediate,则前往 check 阶段
  • check
    • 执行setImmediate
  • close callbacks

重绘和回流(Repaint & Reflow)

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。
重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:

  • 页面初次渲染
  • 浏览器窗口大小改变
  • 元素尺寸、位置、内容发生改变
  • 元素字体大小变化
  • 添加或者删除可见的 dom 元素
  • 激活 CSS 伪类(例如::hover)
  • 查询某些属性或调用某些方法
    • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()

回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。
实践:
css

  • 避免使用table布局
  • 将动画效果应用到position属性为absolute或fixed的元素上

javascript

  • 避免频繁操作样式,可汇总后统一 一次修改
  • 尽量使用class进行样式修改
  • 减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入
  • 极限优化时,修改样式可将其display: none后修改
  • 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住

style标签写在body后与body前有什么区别
写在head标签中利于浏览器逐步渲染(resources downloading->CSSOM+DOM->RenderTree(composite)->Layout->paint)
写在body标签后由于浏览器以逐行方式对html文档进行解析,当解析到写在尾部的样式表(外联或写在style标签)会导致浏览器停止之前的渲染,等待加载且解析样式表完成之后重新渲染,在windows的IE下可能会出现FOUC现象(即样式失效导致的页面闪烁问题)

web标准、可用性、可访问性


可用性(Usability):产品是否容易上手,用户能否完成任务,效率如何,以及这过程中用户的主观感受可好,是从用户的角度来看产品的质量。可用性好意味着产品质量高,是企业的核心竞争力
可访问性(Accessibility):Web内容对于残障用户的可阅读和可理解性
可维护性(Maintainability):一般包含两个层次,一是当系统出现问题时,快速定位并解决问题的成本,成本低则可维护性好。二是代码是否容易被人理解,是否容易修改和增强功能。

**cookies,sessionStorage 和 localStorage **

介绍

  • cookie 是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。
  • cookie 数据始终在同源的 http 请求中携带(即使不需要),也会在浏览器和服务器间来回传递。
  • sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。

存储大小

  • cookie 数据大小不能超过 4k。
  • sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大。

有期时间

  • localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
  • sessionStorage 数据在当前浏览器窗口关闭后自动删除。
  • cookie 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭。

V8垃圾回收机制


垃圾回收: 将内存中不再使用的数据进行清理,释放出内存空间。V8 将内存分成 新生代空间 和 老生代空间
新生代空间: 用于存活较短的对象

  • 又分成两个空间: from 空间 与 to 空间
  • Scavenge GC算法: 当 from 空间被占满时,启动 GC 算法
    • 存活的对象从 from space 转移到 to space
    • 清空 from space
    • from space 与 to space 互换
    • 完成一次新生代GC

老生代空间: 用于存活时间较长的对象

  • 从 新生代空间 转移到 老生代空间 的条件
    • 经历过一次以上 Scavenge GC 的对象
    • 当 to space 体积超过25%
  • 标记清除算法: 标记存活的对象,未被标记的则被释放
    • 增量标记: 小模块标记,在代码执行间隙执,GC 会影响性能
    • 并发标记(最新技术): 不阻塞 js 执行
  • 压缩算法: 将内存中清除后导致的碎片化对象往内存堆的一端移动,解决 内存的碎片化

渐进增强和优雅降级


渐进增强 :针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
优雅降级 :一开始就构建完整的功能,然后再针对低版本浏览器进行兼容

判断浏览器类型

  var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
    var isOpera = userAgent.indexOf("Opera") > -1;
    //判断是否Opera浏览器
    if (isOpera) {
        return "Opera"
    }; 
    //判断是否Firefox浏览器
    if (userAgent.indexOf("Firefox") > -1) {
        return "FF";
    } 
    //判断是否chorme浏览器
    if (userAgent.indexOf("Chrome") > -1){
		return "Chrome";
    }
    //判断是否Safari浏览器
    if (userAgent.indexOf("Safari") > -1) {
        return "Safari";
    } 
    //判断是否IE浏览器
    if (userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera) {
        return "IE";
    }
    //判断是否Edge浏览器
    if (userAgent.indexOf("Trident") > -1) {
        return "Edge";
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

获取URL中参数值

正则法

function getQueryString(name) {
    var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
    var r = window.location.search.substr(1).match(reg);
    if (r != null) {
        return unescape(r[2]);
    }
    return null;
}
// 这样调用:
alert(GetQueryString("参数名1"));
alert(GetQueryString("参数名2"));
alert(GetQueryString("参数名3"));
1
2
3
4
5
6
7
8
9
10
11
12

split拆分法

function GetRequest() {
    var url = location.search; //获取url中"?"符后的字串
    var theRequest = new Object();
    if (url.indexOf("?") != -1) {
        var str = url.substr(1);
        strs = str.split("&");
        for(var i = 0; i < strs.length; i ++) {
            theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]);
        }
    }
    return theRequest;
}
var Request = new Object();
Request = GetRequest(
);
// var 参数1,参数2,参数3,参数N;
// 参数1 = Request['参数1'];
// 参数2 = Request['参数2'];
// 参数3 = Request['参数3'];
// 参数N = Request['参数N'];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

又见正则
通过JS获取url参数,这个经常用到。比如说一个url:http://wwww.jb51.net/?q=js,我们想得到参数q的值,那可以通过以下函数调用即可

function GetQueryString(name) {  
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");  
    var r = window.location.search.substr(1).match(reg);  //获取url中"?"符后的字符串并正则匹配
    var context = "";  
    if (r != null)  
         context = r[2];  
    reg = null;  
    r = null;  
    return context == null || context == "" || context == "undefined" ? "" : context;  
}
alert(GetQueryString("q"));
1
2
3
4
5
6
7
8
9
10
11

单个参数的获取方法

function GetRequest() {
   var url = location.search; //获取url中"?"符后的字串
   if (url.indexOf("?") != -1) {    //判断是否有参数
      var str = url.substr(1); //从第一个字符开始 因为第0个是?号 获取所有除问号的所有符串
      strs = str.split("=");   //用等号进行分隔 (因为知道只有一个参数 所以直接用等号进分隔 如果有多个参数 要用&号分隔 再用等号进行分隔)
      alert(strs[1]);          //直接弹出第一个参数 (如果有多个参数 还要进行循环的)
   }
}
1
2
3
4
5
6
7
8

三种减少页面加载时间的方法

  1. 尽量减少页面中重复的HTTP请求数量
    比较直接的理解就是要减少调用其他页面、文件的数量。我们在使用css格式控制的时候,经常会采用background载入很多图形文件,而每个background的图像都会产生1次HTTP请求,一般我们为了让页面生动活泼会大量使用background来加载背景图,要改善这个状况,可以采用css的1个有用的background-position属 性来加载背景图,我们将需要频繁加载的多个图片合成为1个单独的图片,需要加载时可以采用:background:url(....) no-repeat x-offset y-offset;的形式加载即可将这部分图片加载的HTTP请求缩减为1个。
  2. 服务器开启gzip压缩
    即将需要传输的内容压缩后传输到客户端再解压,这样在网络上传输的 数据量就会大幅减小。通常在服务器上的Apache、Nginx可以直接开启这个设置,也可以从代码角度直接设置传输文件头,增加gzip的设置,也可以从 负载均衡设备直接设置。不过需要留意的是,这个设置会略微增加服务器的负担。建议服务器性能不是很好的网站,要慎重考虑。
  3. css样式的定义放置在文件头部
  4. Javascript脚本放在文件末尾
    我们都知道网页文件的载人是从上到下的加载的,而有很多Javascript脚本执行效率比较低下,或者在网页前面都不需要执行的,如果将这些脚本放置到页面比较靠前的位置,很可能会导致网站内容载入速度下降甚至无**常加载,所以一般将这些脚本放置在网页文件末尾,一定要放 置在前面的脚本要改用所谓的“后载入”方式加载,在主体网页加载完成后再加载,防止其影响到主体网页的加载速度。
  5. 压缩Javascript、CSS代码
    一般js、css文件中存在大量的空格、换行、注释,这些利于阅读,如果能够压缩掉,将会很有利于网络传输。这方面的工具也有很多,可以在百度里搜索一下关键字“css代码压缩”,或者“js代码压缩”将会发现有很多网站都提供这样的功能,当然了你也可以自己写程序来做这个工作,如果你会的话。就拿我们这个网站来说吧。刚开始上传这个网站的时候,我的很多Css代码都没有压缩,后面发现了这个问题,我就上网找了相关的网站的压缩代码的功能,最后就把很多CSS文件都压缩了。这个压缩比率还是比较高的,一般都有50%左右。由此可见,这个代码压缩对于网页的加载还是很有用的。
  6. Ajax采用缓存调用
    Ajax调用都采用缓存调用方式,一般采用附加特征参数方式实现,注意其中的<script src=”xxx.js?{VERHASH}”,{VERHASH}就是特征参数,这个参数不变化就使用缓存文件,如果发生变化则重新下载新文件或更新信息。
  7. 尽可能减少DCOM元素
    这个很好理解,就是尽可能减少网页中各种<>元素数量,例如
    的冗余很严重,而我们完全可以用取代之。
  8. 使用多域名负载网页内的多个文件、图片
  9. 使用CDN
  10. 在服务器端配置control-cache  last-modify-date
  11. 在服务器配置Entity-Tag     if-none-match

CI/CD流程


ci(持续构建)
代码提交后触发自动化的单元测试,代码预编译,构建镜像,上传镜像等.
cd(持续发布)
持续发布则指将构建好的程序发布到各种环境,如预发布环境,正式环境

首屏时间、白屏时间

不同的浏览器对于CSS和HTML的处理方式不同,有的是等待CSS加载完成之后,对HTML元素进行渲染和展示(白屏问题)。有的是先对HTML元素进行展示,然后等待CSS加载完成之后重新对样式进行修改(FOUC无样式内容闪烁)
Performance 接口可以获取到当前页面中与性能相关的信息。
该类型的对象可以通过调用只读属性 Window.performance 来获得。
白屏时间:

performance.timing.responseStart - performance.timing.navigationStart
1

首屏时间

window.onload = () => {
new Date() - performance.timing.responseStart
}
1
2
3

域名收敛


PC 时代为了突破浏览器的域名并发限制。有了域名发散。
浏览器有并发限制,是为了防止DDOS攻击。
域名收敛:就是将静态资源放在一个域名下。减少DNS解析的开销。
域名发散:是将静态资源放在多个子域名下,就可以多线程下载,提高并行度,使客户端加载静态资源更加迅速。
域名发散是pc端为了利用浏览器的多线程并行下载能力。而域名收敛多用与移动端,提高性能,因为dns解析是是从后向前迭代解析,如果域名过多性能会下降,增加DNS的解析开销。

最后更新时间: 10/8/2019, 7:41:17 PM