add: 页面渲染性能优化

This commit is contained in:
qianguyihao 2021-01-14 22:01:44 +08:00
parent cd8001f25e
commit dd446f7a00
3 changed files with 593 additions and 1 deletions

View File

@ -361,10 +361,233 @@ CSS 加载不会阻塞 DOM tree 解析,但是会阻塞 DOM Tree 渲染,也
- Google 字体库因为某些不可抗拒原因应该使用国内托管服务 - Google 字体库因为某些不可抗拒原因应该使用国内托管服务
### 6CSS 动画优化 ### 6CSS 动画优化
- 尽量避免同时出现过多动画 - 尽量避免同时出现过多动画
- 延迟动画初始化让其他的重要的CSS样式优先渲染 - 延迟动画初始化让其他的重要的CSS样式优先渲染
- 结合 SVG - 结合 SVG
## JavaScript 总体优化
### 提升 JavaScript 文件加载性能
加载元素的顺序 CSS 文件放在 <head> JavaScript 文件放在 <body>
### JavaScript 变量和函数优化
- 尽量使用 id 选择器
- 尽量避免使用 eval
- JavaScript 函数尽可能保持简洁
- 使用事件节流函数
- 使用事件委托
### JavaScript 动画优化
- 避免添加大量 JavaScript 动画
- 尽量使用 CSS3 动画
- 尽量使用 Canvas 动画
- 合理使用 requestAnimationFrame 动画代替 setTimeoutsetInterval
- requestAnimationFrame可以在正确的时间进行渲染setTimeoutcallback和setIntervalcallback无法保证 callback 回调函数的执行时机
### 合理使用缓存
- 合理缓存 DOM 对象
- 缓存列表长度
- 使用可缓存的 Ajax
## JavaScript 缓存优化
### Cookie
通常由浏览器存储然后将 Cookie 与每个后续请求一起发送到同一服务器收到HTTP 请求时服务器可以发送带有 Cookie header 可以给 Cookie 设置有效时间
应用
- 会话管理登录名购物车商品游戏得分或服务器应要记录的其他任何内容
- 个性化用户首选项主题或其他设置
- 跟踪记录和分析用户行为比如visitkey
### sessionStorage
创建一个本地存储的键/值对
应用
- 缓存
- 页面应用页面之间传值
### LocalStorage
本地存储
应用于
- 缓存静态文件内容 JavaScript /CSS比如百度M站首页
- 缓存不常变更的 API 接口数据
- 储存地理位置信息
- 浏览在页面的具体位置
### IndexedDB
索引数据库
应用
- 客户端存储大量结构化数据
- 没有网络连接的情况下使用比如 Google Doc石墨文档
- 将冗余很少修改但经常访问的数据以避免随时从服务器获取数据
## JavaScript 模块化加载方案和选型
- CommonJS
旨在 Web 浏览器之外为 JavaScript 建立模块生态系统Node.js 模块化方案受 CommonJS
- AMD (Asynchronous Module Definition)异步模块定义规范
RequireJS 模块化加载器基于 AMD API 实现
- CMD Common Module Definition通用模块定义规范
SeaJS 模块化加载器遵循 CMD API 编写
- ES6 import
## 减少回流和重绘重要举措
### CSS
- 避免过多样式嵌套
- 避免使用 CSS 表达式
- 使用绝对定位可以让动画元素脱离文档流
- 避免使用 table 布局
- 尽量不使用 float 布局
- 图片最好设置好 width height
- 尽量简化浏览器不必要的任务减少页面重新布局
- 使用 Viewport 设置屏幕缩放级别
- 避免频繁设置样式最好把新 style 属性设置完成后进行一次性更改
- 避免使用引起回流/重绘的属性最好把相应变量缓存起来
### JavaScript
- 最小化回流和重排为了减少回流发生次数避免频繁或操作 DOM可以合并多次对 DOM 修改然后一次性批量处理
- 控制绘制过程和绘制区域绘制过程开销比较大的属性设置应该尽量避免减少使用同时减少绘制区域范围
## DOM 编程优化的式方法
### 控制 DOM 大小
众所周知页面交互卡顿和流畅度很大一部分原因就是页面有大量 DOM 元素想象一下从一个上万节点的 DOM 树上使用 querySelectorAll getElementByTagName 方法查找某一个节点是非常耗时的另外元素绑定事件事件冒泡和事件捕获的执行也会相对耗时
通常控制 DOM 大小的技巧包括
- 合理的业务逻辑
- 延迟加载即将呈现的内容
### 简化 DOM 操作
对DOM节点的操作统一处理后再统一插入到 DOM Tree中
可以使用 fragment尽量不在页面 DOM Tree 里直接操作
现在流行的框架 AngularReactVue 都在使用虚拟 DOM 技术通过 diff 算法简化和减少 DOM 操作
## 静态文件压缩工具介绍
HTML 压缩工具
- html-minifierhttps://www.npmjs.com/package/html-minifier
CSS 压缩工具
- clean-csshttps://www.npmjs.com/package/clean-css
JavaScript 压缩工具
- uglify-jshttps://www.npmjs.com/package/uglify-js
- 使用方法uglifyjs in.js -o out.js
## 静态文件打包方案
- 公共组件拆分
- 压缩 JavaScript /CSS/图片
- 合并 JavaScript /CSS 文件合并CSS Sprite
- Combo JavaScript /CSS 文件
## 静态文件版本号更新策略
缓存更新CDN ng 后台刷新文件路径更新文件header头
文件 name.v1-v100.js
- 大功能迭代每次新增一个大版本比如由 v1 v2
- 小功能迭代新增加 0.0.1 或者 0.1.0比如从 v1.0.0 v1.0.1
- 年末 ng 统一配置所有版本 302 至最新版
时间戳.文件 name.js以每次上线时间点做差异
hash.文件以文件内容 hash 值做 key
## 前端构建工具介绍和选型建议
### 常用构建工具
- Gulp通过流Stream来简化多个任务间的配置和输出配置代码相对较少
- Webpack预编译中间文件在内存中处理支持多种模块化配置相对很简单
- FIS
### webpack 打包优化
- 定位体积大的模块
- 删除没有使用的依赖
- 生产模式进行公共依赖包抽离
- 开发模式进行 DLL & DllReference 方式优化

View File

@ -0,0 +1,369 @@
## 浏览器渲染过程
![](https://img.smyhvae.com/20210114_2115.png)
1. 浏览器解析 HTML生成 DOM TreeParse HTML
2. 浏览器解析 CSS生成 CSSOMCSS Object ModelTree
3. JavaScript 会通过 DOM API CSSOM API 来操作 DOM Tree CSS Rule Tree浏览器将 DOM Tree CSSOM Tree 合成渲染树Render Tree
4. 布局Layout根据生成的 Render Tree进行回流以计算每个节点的几何信息位置大小字体样式等等
5. 绘制Painting根据渲染树和回流得到的几何信息得到每个节点的绝对像素
6. 展示Display将像素发送给图形处理器GPU展示在页面上
## 页面渲染技术方案总览
**服务端渲染**
- 后端同步渲染同构直出BigPipe
**客户端渲染**
- JavaScript 渲染静态化前后端分离单页面应用
- Web AppReactVuePWA
- Hybrid AppPhoneGap AppCan
- 跨平台开发RN Flutter 小程序等
- 原生 AppiOS Android
建议
- 依赖业务形式依赖团队规模依赖技术水平
## 静态化技术方案
静态化是使动态化的网站生成静态 HTML 页面以供用户更好访问的技术一般分为纯动态化和伪动态化
技术优势
- 提高了页面访问速度降低了服务器的负担因为访问页面时不需要每次去访问数据库
- 提高网站内容被搜索引擎搜索到的几率因为搜索引擎更喜欢静态页面
- 网站更稳定如果后端程序数据库出现问题会直接影响网站的正常访问而静态化页面有缓存更不容易出现问题
技术不足
- 服务器存储占用问题因为页面量级在增加要占用大量硬盘空间
- 静态页面中的链接更新问题会有死链或者错误链接问题
技术实现
- 跑定时任务将已有的动态内容进行重定生成静态的 HTML 页面
- 利用模板技术将模板引擎中模板字符替换为从数据库字段中取出来的值 同时生成 HTML 文件
协作方式
- 前端统一写好带有交互的完整静态页面
- 后端拆分出静态页面文件并嵌套在后端模板文件中
选型建议后端研发人员充分又需要考虑用户体验服务器负载的业务
## 前后端分离技术与实现
前后端分离是指研发人员分离业务代码分离后端实现业务接口前端渲染页面
技术实现
- 后端只负责功能接口实现提供按照约定的数据格式并封装好的 API 接口
- 前端负责业务具体实现获取到 API 接口数据后进行页面模板拼接和渲染独立上线
协作方式
- 前端负责实现页面前端交互根据后端 API 接口拼装前端模板
- 后端专注于业务功能实现和 API 接口封装
技术优势
- 团队更加专注
- 提升了开发效率
- 增加代码可维护性
技术架构
- 后端架构JavaC++PHP + Nginx使用微服务比如 Dubbo 等实现业务的解耦所有的服务使用某种协议提供不同的服务比如 JSF
- 前端架构使用 AngularReactVue 前端框架并部署页面至 CDN
- 前端架构 2使用 AngularReactVue 前端框架并部署在 Node Server
技术不足
- 因为前端需要负责一大部分业务逻辑实现和服务端同步静态化需要前端人力非常多
- 页面数据异步渲染不利于 SEO搜索引擎更喜欢纯静态页面
选型建议
- 这是大型互联网公司正在采用的开发模式一句话如果考虑用户体验以及前端人力够用就可以积极采用
## 单页面应用技术方案
单页应用single-page application缩写 SPA通过动态重写当前页面来与用户交互而非传统的从服务器重新加载整个新页面这种方法在使用过程中不需要重新加载页面避免了页面之间切换打断用户体验使应用程序更像一个桌面应用程序
技术优点
- 不错的加载速度用户往往感觉页面加载非常快因为一进入页面就能看到页面元素
- 良好的交互体验进行局部渲染避免不必要的页面间跳转和重复渲染
- 前后端职责分离前端进行页面交互逻辑后端负责业务逻辑
- 减轻服务器负载服务器只处理数据接口输出不用考虑页面模板渲染和 HTML 展示
技术缺点
- 开发成本相对较高
- 首次页面加载时间过多
- SEO 难度比较大
技术实现
- 使用 ReactVue 框架可以很好的
## BigPipe 简介和工作模式
BigPipe 通过将页面加载到称为 Pagelet 的小部件中来加快页面渲染速度并允许浏览器在 PHB 服务器呈现页面的同时一直请求页面不同区块的结构类似一个传输管道
**技术实现**
1. 浏览器从服务器请求页面
2. Server 迅速呈现一个包含 <head> 标记的页面框架以及一个包含空 div 元素的主体这些元素充当 Pagelet 的容器由于该页面尚未完成因此与浏览器的 HTTP 连接保持打开状态
3. 浏览器将开始下载 bigpipe.js 文件然后它将开始呈现页面
4. PHP 服务器进程仍在执行并且一次构建每个 Pagelet Pagelet 完成后其结果将在`<script> BigPipe.onArrive</ script>` 标记内发送到浏览器
5. 浏览器将收到的 html 代码注入正确的位置如果小页面需要任何 CSS 资源则也将下载这些 CSS 资源
6. 接收完所有的页面集之后浏览器将开始加载那些页面集所需的所有外部 JavaScript 文件
7. 下载 JavaScript 浏览器将执行所有内联 JavaScript
## 同构直出技术方案
一套代码既可以在服务端运行又可以在客户端运行这就是同构Universal
技术优势
- 性能: 降低首屏渲染时间
- SEO: 服务端渲染对搜索引擎的爬取有着天然的优势
- 兼容性: 有效规避客户端兼容性问题比如白屏
- 代码同构直接上线两个版本利于灾备
技术实现
- next.js服务器端渲染 React 组件框架参考查看https://nextjs.org/, React 采用 ReactDOMServer 调用 renderToString() 方法。
- gatsbyjs服务端 React 渲染框架参考查看 https://www.gatsbyjs.org/)。
- nuxt.js服务器端渲染 Vue 组件框架参考查看https://nuxtjs.org/, Vue 采用 vue-server-renderer 调用 renderToString() 方法。
协作方式
- 后端专注于业务功能实现和 API 接口封装
- 前端负责实现页面前端交互根据后端 API 接口拼装前端模板页面渲染以及服务器维护
选型建议
- 前端要处理 Node server 的机器环境代码部署日志容灾监控等以往后端人员需要具备运维知识前端人员的综合能力要求会比以往要高
- 前端项目开发周期变长了需要事先和产品运营沟通排期问题
## PWA 技术方案和实现思路
Progressive Web App简称 PWAPWA 应用是使用特定技术和标准模式来开发的 Web 应用这将同时赋予它们 Web 应用和原生应用的特性
技术优势
- 用户可以用手机屏幕启动应用即使在离线状态或者弱网下通过事先缓存的资源也可正常加载运行当前应用可以完全消除对网络的依赖从而给用户非常可靠的体验
- 因为预先缓存了资源部分资源无须经过网络即秒开页面
- 和移动设备上的原生应用一样具有沉浸式的用户体验
- 可以给用户发送离线推送消息
技术实现
- 全站改造成 HTTPS没有 HTTPS 就没有 Service Worker
- 应用 Service Worker 技术提升性能离线提供静态资源文件提升首屏用户体验
- 使用 App Manifest
- 最后可以考虑离线消息推送等功能
浏览器兼容性
- ServiceWorkerGlobalScope API88%
- Web App Manifest 83%
## 页面加载策略优化
- 懒加载
- 预加载
- 预渲染
- 按需加载
下面具体展开讲讲
### 懒加载
懒加载也叫延迟加载指的是长网页中延迟加载特定元素可以是图片也可以是 JS/CSS 文件当然也可以是 JavaScript 的特定函数和方法以下简称懒加载元素
好处可以减少当前屏无效资源的加载
技术实现举例把页面上懒加载元素src 属性设置为空字符把真实的 src 属性写在 data-lazy 属性中当页面滚动的时候监听 scroll 事件如果懒加载元素在可视区域内就把图片的 src 属性或者文件 URL 路径设置成 data-lazy 属性值
### 预加载
可以使用预加载让浏览器来预先加载某些资源比如图片JS/CSS/模板而这些资源是在将来才会被使用到的简单来说就是将所需资源提前加载到浏览器本地这样后面在需要使用的时候就可以直接从浏览器缓存中取了而不用再重新开始加载
使用场景如果你希望这个资源能尽快显示给用户就可以使用预加载
好处减少用户后续加载资源等待的时间
**技术实现举例**
1. HTML 标签
```html
<img src="https://xxx.jpg" style="display: none" />
```
2使用 Image 对象
```js
const image = new Image();
image.src = 'https://xxx.jpg';
```
3使用 preloadprefetch preconnect
```html
<link rel=preload href=src/style.css as=style>
<link rel="prefetch" href="scr/image.png" />
<link rel="dns-prefetch" href="https://my.com" />
<link rel="preconnect" href="https://my.com" crossorigin />
```
### 预渲染
有一种预加载组件的方式就是提前渲染它在页面中渲染组件但是并不在页面中展示也就是渲染完成后先隐藏起来用的时候再展示
实现举例
```html
<link rel="prerender" href="https://my.com" />
```
### 按需加载
- 常规按需加载 JS 原生jQuery
- 不同 App 按需加载 JS-SDK 脚本文件
- 不同设备按需加载 PC 端和 HTML5 端样式文件
- 不同分辨率按需加载CSS Media Query
React 异步加载举例
```javascript
const componentA = (location, callback) => {
require.ensure(
[],
(require) => {
callback(null, require('modules/componentA'));
},
'componentA'
);
};
const componentB = (location, callback) => {
require.ensure(
[],
(require) => {
callback(null, require('modules/componentB'));
},
'componentB'
);
};
<Router history={history}>
<Route path="/" component={App}>
<Route path="componentA" getComponent={componentA}></Route>
<Route path="componentB" getComponent={componentB}></Route>
</Route>
</Router>;
```
Vue 异步加载举例
```javascript
import Vue from 'vue';
import App from './App.vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const componentA = resolve => require(['src/a.vue' ], resolve);
const componentB = resolve => require(['src/b.vue' ], resolve);
const router = new VueRouter({
routes: [{path:"a”,name:"/a,component:componentA},
{path:"b”,name:"/b,component:componentB}]
})
new Vue({
el: '#app',
router: router,
render: h => h(App)
})
```
## 接口服务调用优化
1接口合并一个页面的众多业务接口和依赖的第三方接口合并为一个部署在集群的接口统一调用以减少页面接口请求数
2接口上 CDN主要基于接口性能考虑我们可以把**不需要实时更新的接口同步至 CDN**等此接口内容变更之后自动同步至 CDN 集群上如果一定时间内未请求到数据会用源站接口再次请求
3接口域名上 CDN增强可用性稳定性
4接口降级核心接口进行降级用基础接口进行业务实现比如千人千面的推荐接口在大促时间点可以直接运营编辑的数据另外如果接口无访问建议使用兜底数据
5接口监控监控接口成功率不只是常说的 TP99也包括弱网超时网络异常网络切换等一段情况的监控情况排查出来问题后需要联合后端运维网络岗位人员一并解决
## 接口缓存策略优化
1Ajax/fetch 缓存前端请求时候带上 cache依赖浏览器本身缓存机制
2本地缓存异步接口数据优先使用本地 localStorage 中的缓存数据
3多次请求接口数据本地无 localStorage 缓存数据重新再次发出 ajax 请求