资源合并与压缩
- 减少 http 请求数量
- 减少请求资源的大小
- html 压缩:HTML 代码压缩就是压缩这些在文本文件中有意义,但是在 HTML 中不显示的字符,包括空格,制表符,换行符等,还有一些其他意义的字符,如 HTML 注释也可以被压缩。
- 如何进行 html 压缩:
- 使用在线网站进行压缩(现代一般使用 webpack 等打包压缩)
- nodejs 提供了 html-minifier 工具
- 后端模板引擎渲染压缩
- css 压缩:删除无效代码、css 语义合并(多使用简写属性)
- 如何进行 css 压缩:
- 使用在线网站进行压缩
- 使用 html-minifier 对 html 中的 css 进行压缩
- 使用 clean-css 对 css 进行压缩
- js 压缩与混乱:
- 无效字符的删除
- 剔除注释
- 代码语义的缩减和优化
- 代码保护
- 如何进行 js 压缩和混乱:
- 使用在线网站进行压缩
- 使用 html-minifier 对 html 中的 js 进行压缩 3· 使用 uglifyjs2 对 js 进行压缩
- 文件合并存在的问题:
- 首屏渲染问题
- 缓存失效问题
- 解决方法:
- 公共库合并
- 不同页面的合并
- 见机行事,随机应变
- 如何进行文件合并:
- 在线网站进行文件合并
- nodejs 实现文件合并
图片相关的优化
- 不同格式图片常用的业务场景
- jpg 有损压缩,压缩率高,不支持透明
- png 支持透明,浏览器兼容好
- webp 压缩程度更好,在 ios webview 有兼容性问题
- svg 矢量图,代码内嵌,相对较小,图片样式相对简单的场景
- CSS 雪碧图
- 把你的网站上用到的一些图片整合到一张单独的图片中
- 减少你的网站的 HTTP 请求数量
- 缺点:整合图片比较大时,一次加载比较慢
- 雪碧图样式生成网站:www.spritecow.com
- 使用 CssSprite 软件一键生成
- 使用构建工具一键生成
- Image inline
- 使用 Base64 的方式将图片的内容内嵌到 html 当中
- 减少你的网站的 HTTP 请求数量
- 使用矢量图
- 使用 SVG 进行矢量图的绘制
- 使用 iconfont 解决 icon 问题
- 在安卓下使用 webp
- Webp 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都非常优秀、稳定和统一。
- 在线转换 webp:zhitu.isux.us
- 使用构建工具 fis3、webpack 的 webp 插件自动批量转换
- 选择:
- 小于 4kb(8kb)的图片通过构建工具自动转为 inline Image,进而减少一次 http 请求
HTML 渲染过程的一些特点
- 顺序执行、并发加载
- 并发加载:一个域名的并发数量有限制的(大约 20 个),所以会设置 3-4 个 cdn 域名进行托管,防止一个域名达到浏览器并发请求的上限
- 是否阻塞
- 依赖关系
- 引入方式
- link:可以利用缓存
- import
css 阻塞
- css head 中阻塞页面的渲染
- css 阻塞 js 的执行(js 执行会修改 DOM 树和 css 样式,故要先等其渲染完毕才执行 js)
- css 不阻塞外部脚本的加载
js 阻塞
- 直接引入的 js 阻塞页面的渲染
- js 不阻塞资源的加载
- js 顺序执行,阻塞后续 js 逻辑的执行
懒加载
- 图片进入可视区域之后再请求资源
- 对于电商网站等图片很多,页面很长的业务场景适用
- 可减少无效资源的加载
- 并发加载的资源过多会阻塞 js 的加载,影响网站的正常使用
- 原生 jquery 实现:zepto.lazyload.js
预加载
- 图片等静态资源在使用之前的提前请求
- 资源使用到时能直接从缓存中加载,提升用户体验
- 页面展示的依赖关系维护
- 预加载方式:
- 直接设置 image 标签的 src,display:none
- 使用 new Image()对象
- 使用 XMLHttpRequest()请求
- 原生 jquery:preload.js. 预加载方式:
- 直接设置 image 标签的 src,display:none
- 使用 new Image()对象
- 使用 XMLHttpRequest()请求
- 原生 jquery:preload.js
重绘和回流
- css 性能让 javascript 变慢
- 虽然浏览器 css 和 dom 渲染引擎和 js 解析引擎是分开的,但是他们会相互作用,渲染引擎会阻塞 js 解析引擎,js 解析引擎也会阻塞渲染引擎。
- 频繁触发重绘与回流,会导致 U 频繁渲染,最终导致 js 变慢
- 回流
- 当 render tree 中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)
- 当页面布局和几何属性改变时就需要回流
- 触发页面重布局的属性:
- 盒子模型相关属性会触发重布局(width height padding margin display border-width border min-height)
- 定位属性及浮动也会触发重布局(top bottom left right position float clear)
- 改变节点内部文字结构也会触发重布局(text-align overflow-y font-weight overflow font-family line-height vertival-align white-space font-size)
- 重绘
- 当 render tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如 background-color。则就叫称为重绘。
- 只触发重绘的属性:
- 和边框背景颜色有关的属性(color border-style border-radius visibility text-decoration background background-image background-position background-repeat background-size outline-color outline outline-style outline-width box-shadow)
- 回流必将引起重绘,而重绘不一定会引起回流
- 新建 DOM 的过程
- 获取 DOM 后分割为多个图层
- 对每个图层的节点计算样式结果(Recalculate style--样式重计算)
- 为每个节点生成图形和位置(Layout-回流和重布局)
- 将每个节点绘制填充到图层位图中(Paint Setup 和 Paint--重绘)
- 图层作为纹理上传至 GPU
- 符合多个图层到页面上生成最终屏幕图像(Composite Layers--图层重组)
- 性能优化:
- 将频繁重绘回流的 DOM 元素单独作为一个独立图层,那么这个 DOM 元素的重绘和回流的影响只会在这个图层中(原则上少创建图层,最终图层重组时会耗费很大性能)。
- Chrome 创建图层的条件
- 3D 或透视变换 CSS 属性(perspective transform:translateZ(0) will-change:transform)
- 使用加速视频解码的 video 节点
- 拥有 3D(WebGL)上下文或加速的 2D 上下文的
<canvas>
节点 - 混合插件(如 Flash)
- 对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素
- 拥有加速 CSS 过滤器的元素
- 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
- 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
- Chrome 创建图层的条件
- 避免使用触发重绘、回流的 CSS 属性
- 实战优化点:
- 用 translate 替代 top 改变,top 会触发 Layout 回流,translate 不会
- 用 opacity 替代 visibility,前提要将 opacity 所修改的图层独立出去。visibility 会触发 Print 重绘,opacity 重绘和回流都不会触发
- 不要一条一条地修改 DOM 的样式,预先定义好 class ,然后修改 DOM 的 className
- 把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow) ,然后你修改 100 次,然后再把它显示出来
- 不要把 DOM 结点的属性值(offsetHeight offsetWidth)放在一个循环里当成循环里的变量,可使用一个变量储存起来它的值,不要多次读取
- 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
- 动画实现的速度的选择,选择合适的动画速度和间隔时间
- 对于动画新建图层
- 启用 GPU 硬件加速
- 将频繁重绘回流的 DOM 元素单独作为一个独立图层,那么这个 DOM 元素的重绘和回流的影响只会在这个图层中(原则上少创建图层,最终图层重组时会耗费很大性能)。
浏览器存储
Cookie
- 用于浏览器端和服务器端的交互:因为 HTTP 请求无状态,所以需要 cookie 去维持客户端状态(设计出来的初衷)
- 客户端自身数据的存储,但是存在限制:
- 作为浏览器存储,大小 4KB 左右
- 需要设置过期时间 expire
- 在 cookie 中设置了 HttpOnly 属性,那么通过 js 脚本将无法读取到 cookie 信息,这样能有效的防止 XSS 攻击
- cookie 存储数据能力逐渐被 localstorage 替代
- cookie 中在相关域名下面——cdn 的流量损耗
- 解决方法:cdn 的域名和主站的域名要分开
LocalStorage
- HTML5 设计出来专门用于浏览器存储的
- 大小为 5M 左右
- 仅在客户端使用,不和服务端进行通信
- 接口封装较好
- 浏览器本地缓存方案
SessionStorage
- 会话级别的浏览器存储
- 大小为 5M 左右
- 仅在客户端使用,不和服务端进行通信
- 接口封装较好
- 对于表单信息的维护
IndexedDB
- IndexedDB 是一种低级 API,用于客户端存储大量结构化数据。该 API 使用索引来实现对该数据的高性能搜索。虽然 WebStorage 对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB 提供了一个解决方案。
- 为应用创建离线版本
txt1. indexedDB 对象:浏览器原生提供indexedDB对象 API: 1. indexedDB.open():打开数据库,第一个参数是数据库名称,格式为字符串,不可省略;第二个参数是数据库版本,是一个大于0的正整数,可省略 2. indexedDB.deleteDatabase():删除一个数据库,参数为数据库的名字 3. indexedDB.cmp():比较两个值是否为 indexedDB 的相同的主键,返回一个整数,表示比较的结果:0表示相同,1表示第一个主键大于第二个主键,-1表示第一个主键小于第二个主键。 2. IDBRequest 对象: 执行indexedDB.open()方法和indexedDB.deleteDatabase()方法会返回这个对象 API: 1. IDBRequest.readyState:等于pending表示操作正在进行,等于done表示操作正在完成。 2. IDBRequest.result:返回请求的结果。如果请求失败、结果不可用,读取该属性会报错。 3. IDBRequest.error:请求失败时,返回错误对象。 4. IDBRequest.source:返回请求的来源(比如索引对象或 ObjectStore)。 5. IDBRequest.transaction:返回当前请求正在进行的事务,如果不包含事务,返回null。 6. IDBRequest.onsuccess:指定success事件的监听函数。 7. IDBRequest.onerror:指定error事件的监听函数。 3. IDBOpenDBRequest 对象:继承 IDBRequest 对象,提供了两个额外的事件监听属性。 API: 1. IDBOpenDBRequest.onblocked:指定blocked事件(upgradeneeded事件触发时,数据库仍然在使用)的监听函数。 2. IDBOpenDBRequest.onupgradeneeded:upgradeneeded事件的监听函数。 4. IDBDatabase 对象:从IDBOpenDBRequest对象的result(event.target.result)属性上面,拿到一个IDBDatabase对象,它表示连接的数据库 属性: 1. IDBDatabase.name:字符串,数据库名称 2. IDBDatabase.version:整数,数据库版本。数据库第一次创建时,该属性为空字符串。 3. IDBDatabase.objectStoreNames:DOMStringList 对象(字符串的集合),包含当前数据的所有 object store 的名字。 4. IDBDatabase.onabort:指定 abort 事件(事务中止)的监听函数。 5. IDBDatabase.onclose:指定 close 事件(数据库意外关闭)的监听函数。 6. IDBDatabase.onerror:指定 error 事件(访问数据库失败)的监听函数。 7. IDBDatabase.onversionchange:数据库版本变化时触发(发生upgradeneeded事件,或调用indexedDB.deleteDatabase()) API: 1. IDBDatabase.close():关闭数据库连接,实际会等所有事务完成后再关闭。 2. IDBDatabase.createObjectStore():创建存放数据的对象仓库,类似于传统关系型数据库的表格,返回一个 IDBObjectStore 对象。该方法只能在versionchange事件监听函数中调用。 3. IDBDatabase.deleteObjectStore():删除指定的对象仓库。该方法只能在versionchange事件监听函数中调用。 4. IDBDatabase.transaction():返回一个 IDBTransaction 事务对象。 5. IDBObjectStore 对象:执行IDBDatabase.createObjectStore()方法返回的一个对象。 属性: 1. IDBObjectStore.indexNames:返回一个类似数组的对象(DOMStringList),包含了当前对象仓库的所有索引。 2. IDBObjectStore.keyPath:返回当前对象仓库的主键。 3. IDBObjectStore.name:返回当前对象仓库的名称。 4. IDBObjectStore.transaction:返回当前对象仓库所属的事务对象。 5. IDBObjectStore.autoIncrement:布尔值,表示主键是否会自动递增。 API: 1. IDBObjectStore.add(): 用于向对象仓库添加数据,返回一个 IDBRequest 对象。该方法只用于添加数据,如果主键相同会报错,因此更新数据必须使用put()方法。第一个参数是键值,第二个参数是主键 2. IDBObjectStore.put(): 用于更新某个主键对应的数据记录,如果对应的键值不存在,则插入一条新的记录。该方法返回一个 IDBRequest 对象。 3. IDBObjectStore.clear(): 删除当前对象仓库的所有记录。该方法返回一个 IDBRequest 对象 4. IDBObjectStore.delete(): 用于删除指定主键的记录。该方法返回一个 IDBRequest 对象 5. IDBObjectStore.count(): 用于计算记录的数量。该方法返回一个 IDBRequest 对象。 6. IDBObjectStore.getKey(): 用于获取主键。该方法返回一个 IDBRequest 对象。 7. IDBObjectStore.get(): 用于获取主键对应的数据记录。该方法返回一个 IDBRequest 对象 8. DBObjectStore.getAll(): 用于获取对象仓库的记录。该方法返回一个 IDBRequest 对象。 9. IDBObjectStore.getAllKeys():用于获取所有符合条件的主键。该方法返回一个 IDBRequest 对象。 10. IDBObjectStore.index(): 返回指定名称的索引对象 IDBIndex 11. IDBObjectStore.createIndex(): 用于新建当前数据库的一个索引。该方法只能在VersionChange监听函数里面调用。 12. IDBObjectStore.deleteIndex(): 用于删除指定的索引。该方法只能在VersionChange监听函数里面调用。 13. IDBObjectStore.openCursor(): 用于获取一个指针对象。 14. IDBObjectStore.openKeyCursor(): 用于获取一个主键指针对象。 6. IDBTransaction 对象 :执行IDBDatabase.transaction()方法返回的对象。用来异步操作数据库事务,所有的读写操作都要通过这个对象进行。 注意: 1. 事务的执行顺序是按照创建的顺序,而不是发出请求的顺序。 2. 事务有可能失败,只有监听到事务的complete事件,才能保证事务操作成功。 属性: 1. IDBTransaction.db:返回当前事务所在的数据库对象 IDBDatabase。 2. IDBTransaction.error:返回当前事务的错误。如果事务没有结束,或者事务成功结束,或者被手动终止,该方法返回null。 3. IDBTransaction.mode:返回当前事务的模式,默认是readonly(只读),另一个值是readwrite。 4. IDBTransaction.objectStoreNames:返回一个类似数组的对象 DOMStringList,成员是当前事务涉及的对象仓库的名字。 5. IDBTransaction.onabort:指定abort事件(事务中断)的监听函数。 6. IDBTransaction.oncomplete:指定complete事件(事务成功)的监听函数。 7. IDBTransaction.onerror:指定error事件(事务失败)的监听函数。 8. IDBTransaction 对象有以下方法。 方法: 1. IDBTransaction.abort():终止当前事务,回滚所有已经进行的变更。 2. IDBTransaction.objectStore(name):返回指定名称的对象仓库 IDBObjectStore。 7. IDBIndex 对象:IDBObjectStore.index()方法可以获取 IDBIndex 对象,IDBIndex 对象代表数据库的索引,通过这个对象可以获取数据库里面的记录 属性: 1. IDBIndex.name:字符串,索引的名称。 2. IDBIndex.objectStore:索引所在的对象仓库。 3. IDBIndex.keyPath:索引的主键。 4. IDBIndex.multiEntry:布尔值,针对keyPath为数组的情况,如果设为true,创建数组时,每个数组成员都会有一个条目,否则每个数组都只有一个条目。 5. IDBIndex.unique:布尔值,表示创建索引时是否允许相同的主键。 API: IDBIndex 对象有以下方法,它们都是异步的,立即返回的都是一个 IDBRequest 对象。 1. IDBIndex.count():用来获取记录的数量。它可以接受主键或 IDBKeyRange 对象作为参数,这时只返回符合主键的记录数量,否则返回所有记录的数量。 2. IDBIndex.get(key):用来获取符合指定主键的数据记录。 3. IDBIndex.getKey(key):用来获取指定的主键。 4. IDBIndex.getAll():用来获取所有的数据记录。它可以接受两个参数,都是可选的,第一个参数用来指定主键,第二个参数用来指定返回记录的数量。如果数据集比较大,建议使用IDBCursor 对象。 5. IDBIndex.getAllKeys():该方法与IDBIndex.getAll()方法相似,区别是获取所有主键。 6. IDBIndex.openCursor():用来获取一个 IDBCursor 对象,用来遍历索引里面的所有条目。 7. IDBIndex.openKeyCursor():该方法与IDBIndex.openCursor()方法相似,区别是遍历所有条目的主键。 8. IDBCursor 对象:代表指针对象,用来遍历数据仓库(IDBObjectStore)或索引(IDBIndex)的记录。一般通过IDBObjectStore.openCursor()方法获得 属性: 1. IDBCursor.source:返回正在遍历的对象仓库或索引。 2. IDBCursor.direction:字符串,表示指针遍历的方向。共有四个可能的值:next(从头开始向后遍历)、nextunique(从头开始向后遍历,重复的值只遍历一次)、prev(从尾部开始向前遍历)、prevunique(从尾部开始向前遍历,重复的值只遍历一次)。该属性通过IDBObjectStore.openCursor()方法的第二个参数指定,一旦指定就不能改变了。 3. IDBCursor.key:返回当前记录的主键。 4. IDBCursor.value:返回当前记录的数据值。 5. IDBCursor.primaryKey:返回当前记录的主键。对于数据仓库(objectStore)来说,这个属性等同于IDBCursor.key;对于索引,IDBCursor.key返回索引的位置值,该属性返回数据记录的主键。 API: 1. IDBCursor.advance(n):指针向前移动 n 个位置。 2. IDBCursor.continue():指针向前移动一个位置。它可以接受一个主键作为参数,这时会跳转到这个主键。 3. IDBCursor.continuePrimaryKey():该方法需要两个参数,第一个是key,第二个是primaryKey,将指针移到符合这两个参数的位置。 4. IDBCursor.delete():用来删除当前位置的记录,返回一个 IDBRequest 对象。该方法不会改变指针的位置。 5. IDBCursor.update():用来更新当前位置的记录,返回一个 IDBRequest 对象。它的参数是要写入数据库的新的值。 9. IDBKeyRange 对象:代表数据仓库(object store)里面的一组主键。根据这组主键,可以获取数据仓库或索引里面的一组记录。 属性: 1. IDBKeyRange.lower:返回下限 2. IDBKeyRange.lowerOpen:布尔值,表示下限是否为开区间(即下限是否排除在范围之外) 3. IDBKeyRange.upper:返回上限 4. IDBKeyRange.upperOpen:布尔值,表示上限是否为开区间(即上限是否排除在范围之外) 静态方法: 1. IDBKeyRange.lowerBound():指定下限。 2. IDBKeyRange.upperBound():指定上限。 3. IDBKeyRange.bound():同时指定上下限。 4. IDBKeyRange.only():指定只包含一个值
PWA
- PWA (Progressive Web Apps)是一种 Web App 新模型并不是具体指某一种前沿的技术或者某一个单一的知识点,我们从英文缩写来看就能看出来,这是一个渐进式的 WebApp ,是通过一系列新的 Web 特性,配合优秀的 UI 交互设计,逐步的增强 Web App 的用户体验。
- 可靠:在没有网络的环境中也能提供基本的页面访问,而不会出现"未连接到互联网"的页面。
- 快速:针对网页渲染及网络数据访问有较好优化。
- 融入(Engaging):应用可以被增加到手机桌面,并且和普通应用一样有全屏、推送等特性。
- 检查是否是 PWA:lighthouse
Service Worker
- Service Worker 是一个脚本,浏览器独立于当前网页,将其在后台运行,为实现一些不依赖页面或者用户交互的特性打开了一扇大门。在未来这些特性将包括推送消息背景后台同步,geofencing (地理围栏定位) ,但它将推出的第一个首要特性,就是拦截和处理网络请求的能力,包括以编程方式来管理被缓存的响应。
- 使用拦截和处理网络请求的能力,去实现一个离线应用
- 使用 Service Worker 在后台运行同时能和页面通信的能力,去实现大规模后台数据的处理
缓存
Cache-Control(Response、Request,http1.1 提出来的)
- max-age
- 服务器告诉客户端,在 0~max-age 这段时间内资源都是最新的,不必向服务端重新发起请求,可以直接从缓存中读取
- max-age 优先级比 expires(过期时间)要更高
- s-maxage
- s-maxage 优先级高于 max-age,但是只对 public 缓存设备(例如 CDN)才生效,此时会重新向 CDN 发起请求,CDN 返回 304 状态码,并将缓存资源发送给客户端
- 在 s-maxage 设置的时间内,CDN 不会更新,超过 s-maxage 后才会去源服务器请求更新资源
- private
- 可从私有服务器读取,如浏览器客户端
- 设置 maxage
- public
- 只能从公有服务器读取,如 CDN
- 设置 s-maxage
- no-cache
- Cache-Control 配置了 no-cache,不会像 max-age 那样直接去浏览器读相关的缓存而完全不发请求到服务端,它会先发起请求去读取服务端信息判断客户端的资源有没有过期
- no-store
- 不使用缓存
Expires(http1.0)
- 缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。
- 告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求
Last-Modified/If-Modified-Since
- 基于客户端和服务端协商的缓存机制
- last-modified —— response header
- if-modified-since —— request header
- 需要与 cache-control 共同使用
- 比 max-age 优先级低
- 缺点:
- 某些服务端不能获取精确的修改时间
- 文件修改时间改了,但文件内容却没有变
Etag/If-None-Match
- 文件内容的 hash 值
- etag —— response header
- if-none-match —— request header
- 需要与 cache-control 共同使用
分级缓存策略
- 200 状态(from cache):这一层由 expires/cache-control 控制
- expires(http 1.0 版有效)是绝对时间
- cache-control(http1.1 版有效),相对时间,两者都存在时 cache-control 覆盖 expires,只要 cache-control 没有失效,浏览器只访问自己的缓存
- 304 状态:这一层由 last-modified/etag 控制
- 当上一层失效时或用户点击 refresh,F5 时,浏览器就会发送请求给服务器,如果服务器端没有变化,则返回 304 给浏览器
- 200 状态:当浏览器本地没有缓存或者上一层失效时,或者用户使用 CTL+F5 强制时,浏览器直接去服务器下载最新数据
服务端优化
多层次的优化方案
- 构建层模板编译
- 数据无关的 prerender 的方式
- 服务端渲染