一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情。
‘优化’,这两个词,在前端这个领域,时时刻刻都能听到,但是具体有哪些,优化的又到底是什么呢?
在我看来,优化无非分为两种,一种是加载资源时的优化,一种是代码执行的优化,具体详情如下:
1.加载时优化
1.1 对资源的压缩
在日常开发中,我们经常会遇到各种各样问题,这个第三方的包可以帮我们解决很多问题,这些第三方包好用是好用,不仅提高的开发效率,也减少了代码量,但是,在上线的时候,这些包就让我们很头疼了,那么此时该如何解决呢,我在这里提供两种解决方案:
- 排除依赖包,利用CDN将体积很大的包通过静态资源的方式引入
- 通过第三方软件来减少包的体积
1.2 http的缓存
HTML5 Application Cache
Application Cache是html5引入的本地存储方案之一,可以构建离线缓存。目前除IE10-外其他浏览器均支持。
使用步骤:
1.增加manifest文件
application cache是通过mannifest文件来管理的,manifest文件是简单的文本文件,内容是需要被缓存供离线使用的文件列表,及不需要被缓存或读取缓存失败的文件控制。
- 文件的第一行必须是 CACHE MANIFEST
- #开头的行作为注释语句
- 网站的缓存不能超过5M
- 文件资源路径可以使用绝对路径也可以使用相对路径
- 文件列表中任意一个缓存失败都会导致整个缓存失效
- 既可以站点使用同一个minifest文件,也可以每个页面使用一个
文件包含3个指令
- CACHE:需要缓存的资源文件,浏览器会自动缓存带有manifest属性的html页面;
- NETWORK:不需要缓存的文件,可以使用通配符;
- FALLBACK:无法访问缓存文件的备选文件,可以使用通配符;
2.服务器的配置
mannifest文件可以使用任意拓展名,但需要在服务器中添加MIME类型匹配,使用apache比较简单,如果使用.manifest作为拓展名在apache配置文件中添加。
AddType text/cache-manifest .appcache
3.在HTML中引用
<html lang="zh" manifest="main.manifest">
注意:千万不要把 manifest 文件本身放在缓存文件列表中,不然浏览器无法更新 manifest 文件,最好是在 manifest 文件的 http-headers 中设置其立即过期
1.3 加载时机的优化
- 路由懒加载
谈到路由懒加载,路由懒加载到底是什么呢?
当我们打开浏览器,访问网站时,此时默认是刚打开就去加载所有页面,路由懒加载就是只加载你当前点击的那个模块。 按需去加载路由对应的资源,提高首屏加载速度(tip:首页不用设置懒加载,而且一个页面加载过后再次访问不会重复加载)。
实现原理
将路由相关的组件,不再直接导入了,而是改写成异步组件的写法,只有当函数被调用的时候,才去加载对应的组件内容。
传统路由配置
import VueRouter from 'vue-router'
import Login from '@/views/login/index.vue'
import Home from '@/views/home/home.vue'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
export default router
路由懒加载的写法
import VueRouter from 'vue-router'
//const Login = ()=> {
// return import('@/views/login/index.vue')
//}
//const Home = ()=> {
// return import('@/views/home/home.vue')
//}
//有return且函数体只有一行,所以省略后为
const Login = ()=> import('@/views/login/index.vue')
const Home = ()=> import('@/views/home/home.vue')
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
export default router
成果:按需去加载路由对应的资源,提高首屏加载速度,代码实现也很简单,但大大的提升了响应速度。
- 图片懒加载
我们知道,图片在客户眼里也就几 MB 罢了,但是对页面加载速度影响最大的就是图片,有的同学可能会说,一张图片而已,写个代码也就几十 KB 罢了,说这些的我想你肯定没做过这类优化,一张图片确实很小,可是一个项目所用到的图片加一起呢,是不是就很大了,所以对于图片很多的页面,为了加速页面的加载速度,很多时候我们需要将页面内未出现在可视区域的图片先不做加载,等滚到可视区域之后再去加载,这样对于页面加载性能上会有很大的提升,同时也提高了用户体验。
原理:
将页面中的img标签src指向一张小图片或者src为空,然后定义data-src(这个属性可以自定义命名,我才用data-src)属性指向真实的图片。src指向一张默认的图片,否则当src为空时也会向服务器发送一次请求,可以指向loading的地址。当载入页面时,先把可视区域内的img标签的data-src属性值负给src,然后监听滚动事件,把用户即将看到的图片加载。这样便实现了懒加载。
示例:
<script>
var num = document.getElementsByTagName('img').length;
var img = document.getElementsByTagName("img");
var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
lazyload(); //页面载入完毕加载可是区域内的图片
window.onscroll = lazyload;
function lazyload() { //监听页面滚动事件
var seeHeight = document.documentElement.clientHeight; //可见区域高度
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
for (var i = n; i < num; i++) {
if (img[i].offsetTop < seeHeight + scrollTop) {
if (img[i].getAttribute("src") == "default.jpg") {
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}
</script>
- 分页时的资源懒加载
遇到这种情况,就一定时后端传过来的数据太多,所以要做分页处理,但是做了分页之后,发现页面很卡顿,那是因为,一进来就发送请求将所有数据拿到并渲染到分页上了,此时,处理方法时,在发送请求前,适当的添加一些限制条件,只有当前页面才发送请求,获取数据。
2. 执行时优化
2.1 操作DOM时,减少重绘和回流
说到这里,也讲一下重绘和回流吧!
回流 (Reflow)
当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。
会导致回流的操作:
- 页面首次渲染
- 浏览器窗口大小发生改变
- 元素尺寸或位置发生改变
- 元素内容变化(文字数量或图片大小等等)
- 元素字体大小变化
- 添加或者删除可见的
DOM元素 - 激活
CSS伪类(例如::hover) - 查询某些属性或调用某些方法
一些常用且会导致回流的属性和方法:
clientWidth、clientHeight、clientTop、clientLeftoffsetWidth、offsetHeight、offsetTop、offsetLeftscrollWidth、scrollHeight、scrollTop、scrollLeftscrollIntoView()、scrollIntoViewIfNeeded()getComputedStyle()getBoundingClientRect()scrollTo()
重绘 (Repaint)
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
如何避免呢?
CSS
- 避免使用
table布局。 - 尽可能在
DOM树的最末端改变class。 - 避免设置多层内联样式。
- 将动画效果应用到
position属性为absolute或fixed的元素上。 - 避免使用
CSS表达式(例如:calc())。
JavaScript
- 避免频繁操作样式,最好一次性重写
style属性,或者将样式列表定义为class并一次性更改class属性。 - 避免频繁操作
DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。 - 也可以先为元素设置
display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 - 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
注意:回流必将引起重绘,重绘不一定会引起回流*
*
2.2 利用 PWA 去执行非DOM操作
PWA全称为“Progressive Web Apps”,渐进式网页应用,Service Worker是其几大核心技术之一。它作为一个独立的线程,是一段在后台运行的脚本。它的出现使得web app也可以具有类似native app的离线使用、消息推送、后台自动更新等能力。\
不过它有以下限制:
- 不能访问 DOM
- 不能使用同步 API
- 需要HTTPS协议(http://localhost 或 http://127.0.0.1也可)
使用步骤:
1、首先,要使用Service Worker,需要添加一个Service Worker的js的文件,然后在我们的html页面中注册对这个文件的引用。
<script>
navigator.serviceWorker
.register('./sw.js')
.then(function (registration) {
// 注册成功
});
</script>
2、其次,我们在js文件中补充Service Worker的生命周期事件。Service Worker生命周期有三部曲:注册,安装和激活。
//一般来说我们需要注册的有3个事件:
self.addEventListener('install', function(event) {
/* 安装后... */
// cache.addAll:把缓存文件加进来,如a.css,b.js
});
self.addEventListener('activate', function(event) {
/* 激活后... */
// caches.delete :更新缓存文件
});
self.addEventListener('fetch', function(event) {
/* 请求资源后... */
// cache.put 拦截请求直接返回缓存数据
});
对于获取文件和缓存文件,Service worker依赖了两个 API:Fetch (通过网络重新获取内容的标准方式) 和 Cache(应用数据的内容存储,此缓存独立于浏览器缓存和网络状态)。
注意:React脚手架create-react-app内置了 PWA 功能
更新机制
以注册文件为service-worker.js为例,每次访问ServiceWorker控制的页面,浏览器都会加载最新的service-worker.js文件,跟当前service-worker.js文件对比,只要内容有任何不同,浏览器都会获取并安装新文件。但是不会立即生效,原有的ServiceWorker还是会运行,只有当ServiceWorker控制的页面全部关闭后,新的ServiceWorker才会被激活。
2.3 GPU加速
浏览器的GPU加速功能是将需要进行动画的元素提升到一个独立的层(layer),这样就可以避免浏览器进行重新布局(Reflow)和绘制(Repaint),将原先的浏览器使用CPU绘制位图来实现的动画效果转为让GPU使用图层合成(composite)来实现,如果两张图层内部没有发生改变,浏览器就不再进行布局和绘制,直接使用GPU的缓存来绘制每个图层,GPU只负责将各个图层合成来实现动画,这就可以充分利用GPU的资源和优势,减轻CPU的负载,可以使动画更流畅。通过改变两张图片之间的相对位置代替绘制一张图片的每一帧来实现动画,虽然视觉效果相同,但省去了许多绘制的时间。
为了让浏览器将动画元素提升到一个独立的层,可以使用transform和opacity属性来实现动画,当设置了这两个属性之一时,浏览器会自动进行这一优化操作(透明度的变化可以通过GPU改变a通道来实现,不需要浏览器进行重绘)。
与此同时,如果动画并不需要对transform和opacity属性做出改变,可以使用其他的方法强制浏览器为这些元素创建单独的层,比如设置一个没有效果的样式:transform:translateZ(0);这不会对元素的实际样式做出改变。但这是一种hack,规范的做法是使用will-change属性,设置它的值为需要做变换的属性,如will-change: left;浏览器就会知道left这个属性会发生变化,因此会开启硬件加速优化性能。