mirror of
https://github.com/qianguyihao/Web.git
synced 2024-11-01 13:34:46 +08:00
378 lines
13 KiB
Markdown
378 lines
13 KiB
Markdown
|
---
|
|||
|
title: 05-页面渲染性能优化
|
|||
|
publish: true
|
|||
|
---
|
|||
|
|
|||
|
<ArticleTopAd></ArticleTopAd>
|
|||
|
|
|||
|
|
|||
|
## 浏览器渲染过程
|
|||
|
|
|||
|
![](https://img.smyhvae.com/20210114_2115.png)
|
|||
|
|
|||
|
1. 浏览器解析 HTML,生成 DOM Tree(Parse HTML)。
|
|||
|
|
|||
|
2. 浏览器解析 CSS,生成 CSSOM(CSS Object Model)Tree。
|
|||
|
|
|||
|
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 App:React、Vue、PWA
|
|||
|
|
|||
|
- Hybrid App:PhoneGap 、AppCan 等
|
|||
|
|
|||
|
- 跨平台开发:RN 、Flutter 、小程序等。
|
|||
|
|
|||
|
- 原生 App:iOS 、Android
|
|||
|
|
|||
|
建议:
|
|||
|
|
|||
|
- 依赖业务形式、依赖团队规模、依赖技术水平。
|
|||
|
|
|||
|
## 静态化技术方案
|
|||
|
|
|||
|
静态化是使动态化的网站生成静态 HTML 页面以供用户更好访问的技术,一般分为纯动态化和伪动态化。
|
|||
|
|
|||
|
技术优势:
|
|||
|
|
|||
|
- 提高了页面访问速度,降低了服务器的负担,因为访问页面时不需要每次去访问数据库。
|
|||
|
|
|||
|
- 提高网站内容被搜索引擎搜索到的几率,因为搜索引擎更喜欢静态页面。
|
|||
|
|
|||
|
- 网站更稳定,如果后端程序、数据库出现问题,会直接影响网站的正常访问,而静态化页面有缓存,更不容易出现问题。
|
|||
|
|
|||
|
技术不足:
|
|||
|
|
|||
|
- 服务器存储占用问题,因为页面量级在增加,要占用大量硬盘空间。
|
|||
|
|
|||
|
- 静态页面中的链接更新问题会有死链或者错误链接问题。
|
|||
|
|
|||
|
技术实现:
|
|||
|
|
|||
|
- 跑定时任务,将已有的动态内容进行重定,生成静态的 HTML 页面。
|
|||
|
|
|||
|
- 利用模板技术,将模板引擎中模板字符替换为从数据库字段中取出来的值, 同时生成 HTML 文件。
|
|||
|
|
|||
|
协作方式:
|
|||
|
|
|||
|
- 前端统一写好带有交互的完整静态页面。
|
|||
|
|
|||
|
- 后端拆分出静态页面文件,并嵌套在后端模板文件中。
|
|||
|
|
|||
|
选型建议:后端研发人员充分,又需要考虑用户体验、服务器负载的业务。
|
|||
|
|
|||
|
## 前后端分离技术与实现
|
|||
|
|
|||
|
前后端分离是指研发人员分离、业务代码分离、后端实现业务接口,前端渲染页面。
|
|||
|
|
|||
|
技术实现:
|
|||
|
|
|||
|
- 后端只负责功能接口实现,提供按照约定的数据格式并封装好的 API 接口。
|
|||
|
|
|||
|
- 前端负责业务具体实现,获取到 API 接口数据后,进行页面模板拼接和渲染,独立上线。
|
|||
|
|
|||
|
协作方式:
|
|||
|
|
|||
|
- 前端负责实现页面前端交互,根据后端 API 接口拼装前端模板。
|
|||
|
|
|||
|
- 后端专注于业务功能实现和 API 接口封装。
|
|||
|
|
|||
|
技术优势:
|
|||
|
|
|||
|
- 团队更加专注
|
|||
|
|
|||
|
- 提升了开发效率
|
|||
|
|
|||
|
- 增加代码可维护性
|
|||
|
|
|||
|
技术架构:
|
|||
|
|
|||
|
- 后端架构:Java、C++、PHP、 + Nginx,使用微服务(比如 Dubbo 等)等实现业务的解耦,所有的服务使用某种协议提供不同的服务(比如 JSF 等) 。
|
|||
|
|
|||
|
- 前端架构:使用 Angular、React、Vue 前端框架并部署页面至 CDN。
|
|||
|
|
|||
|
- 前端架构 2:使用 Angular、React、Vue 前端框架并部署在 Node Server。
|
|||
|
|
|||
|
技术不足:
|
|||
|
|
|||
|
- 因为前端需要负责一大部分业务逻辑实现,和服务端同步、静态化,需要前端人力非常多。
|
|||
|
|
|||
|
- 页面数据异步渲染,不利于 SEO,搜索引擎更喜欢纯静态页面。
|
|||
|
|
|||
|
选型建议:
|
|||
|
|
|||
|
- 这是大型互联网公司正在采用的开发模式,一句话,如果考虑用户体验,以及前端人力够用,就可以积极采用。
|
|||
|
|
|||
|
## 单页面应用技术方案
|
|||
|
|
|||
|
单页应用(single-page application,缩写 SPA),通过动态重写当前页面,来与用户交互,而非传统的从服务器重新加载整个新页面。这种方法在使用过程中不需要重新加载页面,避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。
|
|||
|
|
|||
|
技术优点:
|
|||
|
|
|||
|
- 不错的加载速度:用户往往感觉页面加载非常快,因为一进入页面就能看到页面元素;
|
|||
|
|
|||
|
- 良好的交互体验:进行局部渲染,避免不必要的页面间跳转和重复渲染;
|
|||
|
|
|||
|
- 前后端职责分离:前端进行页面交互逻辑,后端负责业务逻辑;
|
|||
|
|
|||
|
- 减轻服务器负载:服务器只处理数据接口输出,不用考虑页面模板渲染和 HTML 展示。
|
|||
|
|
|||
|
技术缺点:
|
|||
|
|
|||
|
- 开发成本相对较高
|
|||
|
|
|||
|
- 首次页面加载时间过多
|
|||
|
|
|||
|
- SEO 难度比较大
|
|||
|
|
|||
|
技术实现:
|
|||
|
|
|||
|
- 使用 React、Vue 框架可以很好的。
|
|||
|
|
|||
|
## 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,简称 PWA,PWA 应用是使用特定技术和标准模式来开发的 Web 应用,这将同时赋予它们 Web 应用和原生应用的特性。
|
|||
|
|
|||
|
技术优势:
|
|||
|
|
|||
|
- 用户可以用手机屏幕启动应用,即使在离线状态或者弱网下,通过事先缓存的资源,也可正常加载运行当前应用,可以完全消除对网络的依赖,从而给用户非常可靠的体验。
|
|||
|
|
|||
|
- 因为预先缓存了资源,部分资源无须经过网络,即秒开页面。
|
|||
|
|
|||
|
- 和移动设备上的原生应用一样,具有沉浸式的用户体验。
|
|||
|
|
|||
|
- 可以给用户发送离线推送消息。
|
|||
|
|
|||
|
技术实现:
|
|||
|
|
|||
|
- 全站改造成 HTTPS,没有 HTTPS 就没有 Service Worker。
|
|||
|
|
|||
|
- 应用 Service Worker 技术提升性能,离线提供静态资源文件,提升首屏用户体验。
|
|||
|
|
|||
|
- 使用 App Manifest。
|
|||
|
|
|||
|
- 最后可以考虑离线消息推送等功能。
|
|||
|
|
|||
|
浏览器兼容性:
|
|||
|
|
|||
|
- ServiceWorkerGlobalScope API:88%
|
|||
|
|
|||
|
- 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、使用 preload、prefetch 和 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,也包括弱网、超时、网络异常、网络切换等一段情况的监控情况。排查出来问题后,需要联合后端、运维、网络岗位人员一并解决。
|
|||
|
|
|||
|
## 接口缓存策略优化
|
|||
|
|
|||
|
1、Ajax/fetch 缓存:前端请求时候带上 cache,依赖浏览器本身缓存机制。
|
|||
|
|
|||
|
2、本地缓存:异步接口数据优先使用本地 localStorage 中的缓存数据。
|
|||
|
|
|||
|
3、多次请求:接口数据本地无 localStorage 缓存数据,重新再次发出 ajax 请求。
|
|||
|
|