阅读 1889

浏览器工作原理及web 性能优化

浏览器工作原理

一、浏览器简介

  • 分类:现在主要有五大主流浏览器: Chrome, Internet Explorer, Firefox, Safari and Opera.移动端上是Android Browser, iPhone, Opera Mini and Opera Mobile, UC Browser, the Nokia S40/S60 browsers,除了Opera,这些浏览器都是基于WebKit内核的(目前可能有变)。
  • 功能 :根据W3C制定的一系列规范,从服务端请求并渲染资源
  • 普遍外观:地址栏,前进后退,书签,刷新及取消,主页
  • 深层结构:下边主要介绍———渲染引擎及JS引擎
    • 用户界面(User Interface) :包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
    • 浏览器引擎(Browser engine): 在用户界面和呈现引擎之间传送指令。
    • 呈现引擎(Rendering engine): 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
    • 网络(Networking) : 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
    • 用户界面后端(UI backend):用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
    • JavaScript 解释器(JS Interpreter):用于解析和执行 JavaScript 代码。
    • 数据存储(Datapersistence):这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

二、渲染引擎

  • 分类:不同浏览器用不同渲染引擎。

Internet Explorer uses Trident, Firefox uses Gecko, Safari uses WebKit. Chrome and Opera (from version 15) use Blink, a fork of WebKit.WebKit 是一个开源渲染引擎,起初作为Linux platform的引擎,后被 Apple应用于Mac. 详见 webkit.org.

  • 加载:

    • 浏览器根据 DNS 服务器得到域名的 IP 地址
    • 向这个 IP 的机器发送 HTTP 请求
    • 服务器收到、处理并返回 HTTP 请求
    • 浏览器得到返回内容

客户端:如在浏览器输入https://www.baidu.com/,经过 DNS 解析,查到baidu.com对应的 IP(不同时间、地点对应的 IP 可能会不同),浏览器向该 IP 发送 HTTP 请求。

服务端:服务器接收到 HTTP 请求,经计算,返回 HTTP 请求

  • 过程如下:
    在Chrome开发者工具中的Timeline中可以详细看到具体过程。

  • 内容如下:

  • 渲染:

浏览器对请求的呈现。默认渲染引擎可以呈现html,xml及图片。(通过插件)也可以呈现其它数据,比如pdf等。 目前只考虑html和css方面。

渲染主流程:

Figure : Rendering engine basic flow

Figure : WebKit main flow

content tree:在此,渲染引擎解析html文档并将元素转换成DOM节点。

render tree:在此,渲染引擎解析style(外部css文件或内联style)并转换。

这是一个渐进式过程. 为保证好的UED,渲染引擎尽早展现内容. 在开始构建并展现render树之前,它不会等所有html被解析。部分内容被解析并呈现,同时进程继续解析网络中不断请求到的其余内容。

具体渲染过程
  • 根据 HTML 结构生成 DOM 树
  • 根据 CSS 生成 CSSOM
  • 将 DOM 和 CSSOM 整合形成 RenderTree
  • 根据 RenderTree 开始渲染和展示
  • 遇到<script>时,会执行并阻塞渲染

浏览器拿到了 server 端返回的 HTML 内容后,开始解析并渲染。最初拿到的内容就是一堆字符串,必须先结构化成计算机擅长处理的基本数据结构--- DOM 树 (最基本的数据结构之一)。

解析过程中,如果遇到<link href="..."><script src="...">这种外链加载 CSS 和 JS 的标签,浏览器会异步下载,下载过程和上文中下载 HTML 的流程一样。只不过,这里下载下来的字符串是 CSS 或者 JS 格式的。实际应用中,常用媒体类型(media type)和媒体查询(media query)来解除对渲染的阻塞。

<link href="index.css" rel="stylesheet"> <--阻塞-->
<link href="print.css" rel="stylesheet" media="print"><--加载&& !阻塞,仅在print时实用-->
<link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)"><--符合条件时阻塞-->
复制代码
  • 查看下边一段代码:
<p>根据 HTML 结构生成 DOM 树,稍等。。。</p>
<script src="app.js"></script>
<p>根据 CSS 生成 CSSOM,稍等。。。</p>
<script>console.log("inline")</script>
<p>将 DOM 和 CSSOM 整合形成 RenderTree</p>
复制代码

这段html被解析时会被js代码阻塞(加载+执行),因此常把css放在头部(保证渲染),把js放在底部(保证非阻塞)。

  • defer 与 async(区别在于:加载完之后何时执行)
    • defer(延迟):并行执行html解析与js加载,载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。
    • async(异步):与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行,因此async脚本一定时阻塞load事件的。因此,多个 async-script 的执行顺序是不确定的。值得注意的是,向 document 动态添加 script 标签时,async 属性默认是 true.
document.createElement("script").async
// true
复制代码
<script src="app1.js" defer></script>
<script src="app2.js" defer></script>

<script src="init1.js" async></script>
<script src="init2.js" async></script>
复制代码
  • 浏览器的容错机制

您在浏览 HTML 网页时从来不会看到“语法无效”的错误。这是因为浏览器会纠正任何无效内容,然后继续工作。

1. 明显不能在某些外部标签中添加的元素。在此情况下,我们应该关闭所有标签,直到出现禁止添加的元素,然后再加入该元素。
2. 我们不能直接添加的元素。这很可能是网页作者忘记添加了其中的一些标签(或者其中的标签是可选的)。这些标签可能包括:HTML HEAD BODY TBODY TR TD LI(还有遗漏的吗?)。
3. 向 inline 元素内添加 block 元素。关闭所有 inline 元素,直到出现下一个较高级的 block 元素。
4. 如果这样仍然无效,可关闭所有元素,直到可以添加元素为止,或者忽略该标记。
复制代码

CSS 解析

和 HTML 不同,CSS 是上下文无关的语法,可以使用简介中描述的各种解析器进行解析。事实上,CSS 规范定义了 CSS 的词法和语法。

  • WebKit CSS 解析器 WebKit 使用 Flex 和 Bison 解析器生成器,通过 CSS 语法文件自动创建解析器。正如我们之前在解析器简介中所说,Bison 会创建自下而上的移位归约解析器。Firefox 使用的是人工编写的自上而下的解析器。这两种解析器都会将 CSS 文件解析成 StyleSheet 对象,且每个对象都包含 CSS 规则。CSS 规则对象则包含选择器和声明对象,以及其他与 CSS 语法对应的对象。
图:解析 CSS

处理脚本和样式表的顺序

  • 脚本

:遇到 <script>文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。HTML5 增加了一个选项----“defer”,这样不会停止文档解析,而是等到解析结束才执行.

  • 预解析 WebKit 和 Firefox 都进行了这项优化。**在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。**通过这种方式,资源可以在并行连接上加载,从而提高总体速度。请注意,预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。

  • 样式表: 理论上来说,应用样式表不会更改 DOM 树,因此似乎没有必要等待样式表并停止文档解析

  • render树构建: 在 DOM 树构建的同时,浏览器还会构建:render树---按照正确的顺序绘制内容。

  • render树和 DOM 树的关系:呈现器是和 DOM 元素相对应的,但并非一一对应。非可视化的 DOM 元素不会插入呈现树中,例如“head”元素。如果元素的 display 属性值为“none”,那么也不会显示在呈现树中(但是 visibility 属性值为“hidden”的元素仍会显示)。

Web 安全

常见的web攻击方式:

  • SQL 注入:输入时进行了恶意的 SQL 拼装,导致最后生成的 SQL 有问题。攻击方式低端,常出现在比较低端的系统。

  • XSS(Cross Site Scripting,跨站脚本攻击)

前端最常见的攻击方式,很多网站(如 阮一峰博客)都被 XSS 攻击过。

原理:通过某种方式(发布文章、发布评论等)将一段特定的 JS 代码隐蔽地输入进去。JS 代码一旦执行,那可就不受控制了,因为它跟网页原有的 JS 有同样的权限,例如可以获取 server 端数据、可以获取 cookie 等。于是,攻击就这样发生了。

预防:最根本的方式,用正则表达式进行转换处理,如:

< 替换为:&lt;
> 替换为:&gt;
& 替换为:&amp;
‘ 替换为:&#x27;
” 替换为:&quot;
复制代码

另外,还可以通过对 cookie控制,比如对敏感的 cookie 增加http-only限制,让 JS 获取不到 cookie 的内容。

  • CSRF(Cross-site request forgery,跨站请求伪造) 理论基础:借助了一个 cookie的特性,劫持操作者的权限来偷偷地完成某个操作,而不是拿到用户的信息。

预防 :加入各个层级的权限验证,如涉及现金交易,必须要输入密码或者指纹才行等。

浏览器重新渲染(re-render):重绘(repaint)与重排(reflow)

重新渲染,就需要重新生成布局和重新绘制。前者叫做"重排"(reflow),后者叫做"重绘"(repaint)。"重绘"不一定需要"重排","重排"必然导致"重绘"。重排和重绘会不断触发,这是不可避免的。但是,它们非常耗费资源,是导致网页性能低下的根本原因。提高网页性能,就是要降低"重排"和"重绘"的频率和成本,尽量少触发重新渲染。

  • 触发重新渲染的常见情况:
    • 修改DOM
    • 修改样式表
    • 用户事件(比如鼠标悬停、页面滚动、输入框键入文字、改变窗口大小等等)
    • 访问元素属性:
      • offsetTop/offsetLeft/offsetWidth/offsetHeight
      • scrollTop/scrollLeft/scrollWidth/scrollHeight
      • clientTop/clientLeft/clientWidth/clientHeight
      • getComputedStyle()
  • 提高性能的技巧:
    • css:

      • display(none->block)
      • position(absolute,fixed)
      • visibility(hidden)
    • js:

      • 使用优秀类库的Virtual DOM(React,Vue)
      • 注意作用域
        • 避免全局查找
        • 避免with
      • 选择正确的方法
        • 避免没必要的查找(性能部分的一部分是和解决问题的算法和方法相关的)
        • 优化循环
        • 展开循环
        • 避免双重解释
        • 其它
          • 原生方法较快:
          • switch语句较快
          • 位运算符较快
      • 最小化语句数
        • 多个变量声明
        • 插入迭代值
        • 使用数组和对象字面量
      • 优化DOM交互(如下)
    • BOM:

      • window.requestAnimationFrame
      • window.requestIdleCallback
    • DOM:

      • document.createDocumentFragment:最小化现场更新
      • innerHTML:要不使用标准DOM方法创建同样的DOM快得多(内部方法是编译好而非解释执行的)。
      • 事件代理:页面处理程序的数量和页面响应UED速度负相关。事件处理程序本质上是一种函数,是一种对象,存放在内存中,设置大量的事件处理程序会使内存中的对象变多,Web 程序的性能会变得越来越差,用户体验很不好。
      • 注意HTMLCollection:最小化对它们的访问,以下会返回HTMLCollection对象
        • getElementByTagName
        • childNodes
        • attributes
        • 特殊集合:document.forms,document.images etc.
      • cloneNode
      • 集中DOM的读写操作
      • 使用 SSR 后端渲染,数据直接输出到 HTML 中,减少浏览器使用 JS 模板渲染页面 HTML 的时间
    • HTTP: 减少页面体积,提升网络加载

      • 静态资源的压缩合并(JS 代码压缩合并、CSS 代码压缩合并、雪碧图)
      • 静态资源缓存(资源名称加 MD5 戳)
      • 使用 CDN 让资源加载更快

应用示例

  • js中应用到的简单算法

标记名称 描述
O(1) 常数:不管有多少值,执行时间恒定,一般表示简单值和存储在变量中的值(数组元素访问)
O(logn) 对数:执行总时间与数量相关,但要完成算法并不一定获取每个值。如二分查找
O(n) 线性:执行总时间和数量直接相关。如遍历操作
O(n2) 平方:执行总时间与数量有关,每个值至少要获取n次。如插入排序
  • 代码示例

  1. 避免全局查找

测试网页

// bad(3次全局查找) 
function updateUI(){
console.time()
	var images=document.getElementsByTagName('img')
for(var i=0,len=images.length;i<len;i++){
	images[i].title=document.title+'image'+i;
}
return console.timeEnd()
}
// default: 0.5439453125ms
// good(一次全局查找)
function updateUI(){
console.time()
var doc = document
	var images=doc.getElementsByClassName('img')
for(var i=0,len=images.length;i<len;i++){
	images[i].title=doc.title+'image'+i;
}
return console.timeEnd()
}
// default: 0.120849609375ms
复制代码

2.避免不必要的属性查找(见JS常见算法类型) 最简单最便捷的算法是常数O(1)

// 四次查找(5,value,10,sum)
var value = 5
var sum = value + 10 
console.log(sum)
// default: 0.375ms
复制代码

3.使用变量和数组要比访问对象上属性(O(n))更高效。

// good
var values = [1, 2, 3]
var sum = values[0] + values[1]
console.log(sum)
// default: 0.298095703125ms
// bad
var values = {a:1, b:2}
var sum = values.a + values.b
console.log(sum)
// default: 0.302978515625ms
复制代码

4.⚠️注意获取单个值的属性查找

// bad
var query = window.location.href.substring(window.location.href.indexOf('?'))
// default: 0.02783203125ms
// good
var url = window.location.href
var query = url.substring(url.indexOf('?'))
// default: 0.02001953125ms
复制代码
  1. 优化循环
// bad
var values=new Array().fill(10000)
for(var i=0;i<values.length;i++){i}
// default: 0.101806640625ms
// good(使用减值迭代:将终止条件从values.length的O(n)调用简化成了O(1)调用)
for(var i = values.length-1;i>0;i--){}
// default: 0.01708984375ms (一个数量级)
// good(后测试循环:循环部分已完全优化,任何进一步的优化只能在处理语句中进行了),这种写法的弊端就是可读性很差
var i = values.length - 1
if(i > -1){
    do{
        //
    }while(--i >= 0)
}
// default: 0.011962890625ms
复制代码

6.避免双重解释(JS 代码运行的同时必须新启动一个解析器来解析新的代码。实例化一个新的解析器有不容忽视的开销)

eval("alert('hi')")// bad
var say = new Function("alert('hi')")// bad
setTimeout('alert("hi")',500)// bad
alert('hi')// good
var say = function (){
    alert('hi')
}// good
setTimeout(function (){
    alert('hi')
},500)// good
复制代码
  1. 最小化语句数
// 多个变量声明
var count = 0,
    color = 'red',
    values = [],
    now = new Date()
// 插入迭代值
var value = values[i]; i ++ // bad
var value = values[i++] // good
// 使用数组和对象字面量
var values = new Array(); values[0] = 1; values[1] = 2 // bad(3个语句)
var person = new Object();
person.a = 1;
person.b = 2 // bad(3个语句)
var values = [1,2] // good(2个语句创建和初始化数组)
var person = {a:1,b:2}// good(2个语句创建和初始化对象)
复制代码
  1. 优化DOM交互(因DOM内部包含了非常多的属性及方法,DOM 交互非常耗时)
// DOM里的内容
var div = document.createElement('div'), result =  ''
for(var i in div){
    result += i + '  '
}
/*
"align  title  lang  translate  dir  dataset  hidden  tabIndex  accessKey  draggable  spellcheck  autocapitalize  contentEditable  isContentEditable  inputMode  offsetParent  offsetTop  offsetLeft  offsetWidth  offsetHeight  style  innerText  outerText  onabort  onblur  oncancel  oncanplay  oncanplaythrough  onchange  onclick  onclose  oncontextmenu  oncuechange  ondblclick  ondrag  ondragend  ondragenter  ondragleave  ondragover  ondragstart  ondrop  ondurationchange  onemptied  onended  onerror  onfocus  oninput  oninvalid  onkeydown  onkeypress  onkeyup  onload  onloadeddata  onloadedmetadata  onloadstart  onmousedown  onmouseenter  onmouseleave  onmousemove  onmouseout  onmouseover  onmouseup  onmousewheel  onpause  onplay  onplaying  onprogress  onratechange  onreset  onresize  onscroll  onseeked  onseeking  onselect  onstalled  onsubmit  onsuspend  ontimeupdate  ontoggle  onvolumechange  onwaiting  onwheel  onauxclick  ongotpointercapture  onlostpointercapture  onpointerdown  onpointermove  onpointerup  onpointercancel  onpointerover  onpointerout  onpointerenter  onpointerleave  nonce  click  focus  blur  namespaceURI  prefix  localName  tagName  id  className  classList  slot  attributes  shadowRoot  assignedSlot  innerHTML  outerHTML  scrollTop  scrollLeft  scrollWidth  scrollHeight  clientTop  clientLeft  clientWidth  clientHeight  onbeforecopy  onbeforecut  onbeforepaste  oncopy  oncut  onpaste  onsearch  onselectstart  previousElementSibling  nextElementSibling  children  firstElementChild  lastElementChild  childElementCount  onwebkitfullscreenchange  onwebkitfullscreenerror  setPointerCapture  releasePointerCapture  hasPointerCapture  hasAttributes  getAttributeNames  getAttribute  getAttributeNS  setAttribute  setAttributeNS  removeAttribute  removeAttributeNS  hasAttribute  hasAttributeNS  getAttributeNode  getAttributeNodeNS  setAttributeNode  setAttributeNodeNS  removeAttributeNode  closest  matches  webkitMatchesSelector  attachShadow  getElementsByTagName  getElementsByTagNameNS  getElementsByClassName  insertAdjacentElement  insertAdjacentText  insertAdjacentHTML  requestPointerLock  getClientRects  getBoundingClientRect  scrollIntoView  scrollIntoViewIfNeeded  animate  before  after  replaceWith  remove  prepend  append  querySelector  querySelectorAll  webkitRequestFullScreen  webkitRequestFullscreen  attributeStyleMap  scroll  scrollTo  scrollBy  createShadowRoot  getDestinationInsertionPoints  computedStyleMap  ELEMENT_NODE  ATTRIBUTE_NODE  TEXT_NODE  CDATA_SECTION_NODE  ENTITY_REFERENCE_NODE  ENTITY_NODE  PROCESSING_INSTRUCTION_NODE  COMMENT_NODE  DOCUMENT_NODE  DOCUMENT_TYPE_NODE  DOCUMENT_FRAGMENT_NODE  NOTATION_NODE  DOCUMENT_POSITION_DISCONNECTED  DOCUMENT_POSITION_PRECEDING  DOCUMENT_POSITION_FOLLOWING  DOCUMENT_POSITION_CONTAINS  DOCUMENT_POSITION_CONTAINED_BY  DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC  nodeType  nodeName  baseURI  isConnected  ownerDocument  parentNode  parentElement  childNodes  firstChild  lastChild  previousSibling  nextSibling  nodeValue  textContent  hasChildNodes  getRootNode  normalize  cloneNode  isEqualNode  isSameNode  compareDocumentPosition  contains  lookupPrefix  lookupNamespaceURI  isDefaultNamespace  insertBefore  appendChild  replaceChild  removeChild  addEventListener  removeEventListener  dispatchEvent  "
*/
复制代码
  1. 最小化现场更新
// bad
console.time()
var list = document.createElement('ul'),
    item,
    i
for(i=0;i<10000;i++){
    item = document.createElement('li')
    list.appendChild(item)
    item.appendChild(document.createTextNode('item' + i))
}
document.body.appendChild(list)
console.timeEnd()
// default: 26.902099609375ms

// good
console.time()
var list = document.createElement('ul'),
    fragment = document.createDocumentFragment(),
    item,
    i
for(i=0;i<10000;i++){
    item = document.createElement('li')
    fragment.appendChild(item)
    item.appendChild(document.createTextNode('item' + i))
}
document.body.appendChild(fragment)
console.timeEnd()
// VM7017:12 default: 29.4169921875ms ???
// good (使用innerHTML(编译好的而非解释执行的))
console.time()
var list = document.createElement('ul'),
    html = '',
    i
for(i=0;i<10000;i++){
    // list.innerHTML += "<li>Item " + i + "</li>" // 避免
   html += "<li>Item " + i + "</li>"
}
list.innerHTML = html
document.body.appendChild(list)
console.timeEnd()
//default: 25.760986328125ms
复制代码
  1. 最小化对HTMLColletion的访问
console.time()
var images = document.getElementsByTagName('img'),
i,
image,
len,
temp
for(i = 0, len=images.length;i<len;i++){
    image = images[i]// 保存当前image,避免了对images的访问
	temp=image+Image
}
console.timeEnd()
// default: 0.06591796875ms
console.time()
var images = document.getElementsByTagName('img'),
i,
len,
temp
for(i = 0, len=images.length;i<len;i++){
	temp=images[i]+images[i]
}
console.timeEnd()
// VM6253:9 default: 0.115966796875ms
复制代码

静态资源缓存

通过链接名称控制缓存

<script src="a.js"></script>
复制代码

只有内容改变的时候,链接名称才会改变

<script src="b.js"></script>
复制代码

可通过前端构建工具根据文件内容,为文件名称添加 MD5 后缀。

使用CDN(专业的加载优化方案)让资源加载更快

<script src="https://cdn.bootcss.com/zepto/1.0rc1/zepto.min.js"></script>
复制代码

事件节流:函数节流(throttle)与函数去抖(debounce)

  • 概念:
    • 函数节流:如果将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出。也就是说会预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。
    • 函数去抖:如果用手指一直按住一个弹簧(debounce),它将不会弹起直到你松手为止。也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。
  • 为什么要节流?

由于事件频繁被触发,因而频繁执行DOM操作、资源加载等重行为,导致UI停顿甚至浏览器崩溃。

  • 何时考虑节流?

    • window对象的resize、scroll事件;
    • 拖拽时的mousemove事件;
    • 游戏中的mousedown、keydown、mouseenter,mouseover事件;
    • 文字输入input、blur、自动完成的keyup事件等。
  • 具体做法:

// throttle
var throttle = function(delay, action){
  var last = 0return function(){
    var curr = +new Date()
    if (curr - last > delay){
      action.apply(this, arguments)
      last = curr 
    }
  }
}
// debounce
var textarea = document.getElementById('btn')
var timeoutId
textarea.addEventListener('keyup', function () {
    if (timeoutId) {
        clearTimeout(timeoutId)
    }
    timeoutId = setTimeout(function () {
        // 触发 change 事件
    }, 100)
})
复制代码

web性能优化(web performance)

为什么要性能优化

  • 留住用户(Performance is about retaining users)
  • 提升转化率(Performance is about improving conversions)
转化率就是金钱
  • 关乎用户体验(Performance is about the user experience)
  • 以人为本(Performance is about people)

如何去优化

要发送什么(Mind what resources you send)

构建高性能应用程序的有效方法是审核发送给用户的资源。虽然Chrome开发人员工具中的网络面板可以很好地总结给定页面上使用的所有资源,但如果您到目前为止尚未考虑性能,那么知道从哪里开始可能会令人生畏。以下是一些建议:

  • 如果您使用Bootstrap或Foundation来构建UI,请问自己是否有必要。这些抽象添加了浏览器必须下载,解析和应用于页面的大量CSS,所有这些都是在特定于站点的CSS进入图片之前。 Flexbox和Grid在使用相对较少的代码创建简单和复杂布局方面非常出色。由于CSS是一种render阻塞资源,因此CSS框架的开销可能会显着延迟渲染。您应该通过消除不必要的开销来寻求加速渲染,而非尽可能依赖于浏览器中的工具。

  • JavaScript库很方便,但并不总是必要的。以jQuery为例:由于querySelectorquerySelectorAll等方法,元素选择得到了极大的简化。使用addEventListener可以轻松进行事件绑定。classList,setAttributegetAttribute提供了使用类和元素属性的简便方法。如果你必须使用类库,研究更精简的替代品。例如,Zepto是一个较小的jQuery替代品,Preact是React的一个小得多的替代品。

  • 并非所有网站都需要是单页面应用程序(SPA),因为它们经常广泛使用JavaScript。 JavaScript是我们在字节的web字节上提供的最昂贵的资源,因为它不仅必须下载,还必须解析,编译和执行。例如,具有优化前端架构的新闻和博客站点可以像传统的多页体验一样表现良好。

如何发送(Mind how you send resources)

当您知道需要为您的应用发送哪些资源以使其成为您想要的美观和功能时,请考虑下一步如何发送它们。与预见和预防一样,交付对于构建快速用户体验至关重要。

  • 迁移到HTTP / 2。 HTTP / 2解决了HTTP / 1.1中固有的许多性能问题,例如并发请求限制和缺少头压缩。
  • 使用资源提示加快资源交付。 rel = preload是一个这样的资源提示,它允许在浏览器发现它们之前提前获取关键资源。这可以对页面呈现产生明显的积极影响,并在明智地使用时降低交互时间。 rel = preconnect是另一个资源提示,可以掩盖为第三方域上托管的资源打开新连接的延迟。
  • 现代网站平均提供大量JavaScript和CSS。在HTTP / 1环境中将样式和脚本捆绑到大型捆绑包中很常见。这样做是因为大量请求对性能有害。现在不再是HTTP / 2在场的情况,因为多个同时请求更便宜。考虑在webpack中使用代码拆分来限制下载的脚本数量,使其仅限于当前页面或视图所需的内容。将CSS分成较小的模板或特定于组件的文件,并仅包含可能使用它们的资源。

数据规模(Mind how much data you send)

通过一些关于哪些资源适合发送以及如何发送它们的想法,我们将介绍一些限制发送数据的建议:

  1. 配置服务器以压缩资源。压缩会大大减少您发送给用户的数据量,尤其是在涉及文本资产的情况下。 GZIP在这个领域是一种非常优秀的格式,但Brotli压缩可以更进一步。但是,要理解压缩并不是性能问题的全部:一些隐式压缩的文件格式(例如,JPEG,PNG,GIF,WOFF等)不响应压缩,因为它们已经被压缩。
  2. 优化图片以确保您的网站尽可能少地发送图片数据。由于图片构成了网络上每页平均有效负载的很大一部分,因此图片优化代表了提升性能的独特机会。
  3. 客户端提示可用于根据当前网络条件和设备特征定制资源交付。 DPR,Width和Viewport-Width标头可以帮助您使用服务器端代码为设备提供最佳图像,并提供更少的标记。 Save-Data标头可以帮助您为明确要求您的用户提供更轻松的应用程序体验。
  4. NetworkInformation API公开有关用户网络连接的信息。此信息可用于修改较慢网络上的用户的应用程序体验。
  5. 减少请求(HTTP requests reduction)
  6. 文件压缩(File compression)
  7. 优化web缓存(Web caching optimization)

Web Caching Optimization reduces server load, bandwidth usage, and latency. CDNs use dedicated web caching software to store copies of documents passing through their system. Leveraging the browser cache is crucial. It is recommended to have a max-age of 7 days in such cases. This saves server time and makes things altogether faster.

  1. 简化代码(Code minification)
  2. 矢量图替换位图(Replacement of vector graphics)
  3. 避免301 重定向

Redirects are performance killers. Avoid them whenever possible. A redirect will generate additional round-trip times and therefore quickly doubles the time that is required to load the initial HTML document before the browser even starts to load other assets.

  1. 采用基于云的网站监控(Adopt Cloud-based Website Monitoring)

  2. 资源预加载:Pre-fetching是一种提示浏览器预先加载用户之后可能会使用到的资源的方法。

  • 使用dns-prefetch来提前进行DNS解析,以便之后可以快速地访问另一个主机名(浏览器会在加载网页时对网页中的域名进行解析缓存,这样你在之后的访问时无需进行额外的DNS解析,减少了用户等待时间,提高了页面加载速度)。
<link rel="dns-prefetch" href="other.hostname.com">
复制代码
  • 使用prefetch属性可以预先下载资源,不过它的优先级是最低的。
<link rel="prefetch"  href="/some_other_resource.jpeg">
复制代码
  • Chrome允许使用subresource属性指定优先级最高的下载资源(当所有属性为subresource的资源下载完完毕后,才会开始下载属性为prefetch的资源)。
<link rel="subresource"  href="/some_other_resource.js">
复制代码
  • prerender可以预先渲染好页面并隐藏起来,之后打开这个页面会跳过渲染阶段直接呈现在用户面前(推荐对用户接下来必须访问的页面进行预渲染,否则得不偿失)。
<link rel="prerender"  href="//domain.com/next_page.html">
复制代码
  1. SSL certificate/ HTTPS

  2. 热链接保护(Hotlink protection) 限制HTTP引用,以防止他人将您的资源嵌入其他网站。 热链接保护将通过禁止其他网站显示您的图像来节省带宽。

  3. 基础设施(Infrastructure)

  4. 数据库优化(Database Optimization)

  5. 使用webworker

(前端)主流框架主流做法案例(见下一篇)

参考链接

关注下面的标签,发现更多相似文章
评论