前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >漫谈前端性能优化

漫谈前端性能优化

作者头像
一粒小麦
发布2019-08-22 19:38:36
7350
发布2019-08-22 19:38:36
举报
文章被收录于专栏:一Li小麦一Li小麦

漫谈前端性能优化

url加载到页面加载完成发送了什么?

这是一个互联网从业者时常必须关注的问题。

作为前端关注浏览器。这个阶段,就是性能可以优化的时间。

  • 输入url=>通过dns,把url解析为ip
  • 和ip建立tcp链接(tcp/ip协议),发送http请求
  • 服务端接收请求,进行业务处理
  • 浏览器收到html开始渲染
  • html to dom
  • 解析css 为 css-tree
  • 加载js
  • 执行js

前端性能优化有两个大方向:

  1. 少加载文件(图片,代码)
  2. 少执行代码

现代浏览器的工作方式:推荐阅读《How Browser Works》

https://www.cnblogs.com/zjstar12/archive/2012/01/12/2320408.html

dns

前端在这块能做的事情比较少,

打开淘宝的网页源码,会发现有些标签预先获取一些dns。

淘宝首页加载时,这些域名的dns结果已经拿回来了。

TCP/IP协议

推荐阅读:图解TCP/IP,图解http,http权威指南,

在软件层面来说,最底层就是ip协议。ip最主要做的就是寻址。不负责数据内容。

TCP负责数据的完整性和有序。一旦发生丢包等,立即补发。(慢)

UDP和TCP是一层的协议,只发数据,但不管别的。主要使用场景:语音,视频等。(dns也是用的)

http:每次建立连接之前都会触发三次握手。了解一下:

第一次握手(你在不?)

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手(我在哟)

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手(那我发数据了哟)

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

优化方向

解决文件:图片绝对是大头。

减少文件体积
  • js,css压缩
  • 图片
    • 雪碧图(相对没那么推荐了,更新麻烦)。
    • 色彩丰富的小图=>png。
    • 轮播图之类=>jpg
    • base64还是svg好?前者弱点明显,只支持小图,后者作为矢量图,不失真,但增加了请求,牺牲了计算量。不能一刀切。
    • webP很优秀。但是在ios会有兼容性问题。
  • gzip代码压缩:至少减少一半。(见公众号文章:优化两则)

HTTP 压缩就是以缩小体积为目的,对 HTTP 内容进行重新编码的过程 Gzip 压缩背后的原理,是在一个文本文件中找出一些重复出现的字符串、临时替换它们,从而使整个文件变小。 根据这个原理,文件中代码的重复率越高,那么压缩的效率就越高,使用 Gzip 的收益也就越大。反之亦然。基本上来说,Gzip都是服务器干的活,比如nginx

减少文件请求次数
  • 懒加载(后面有实例)
  • http缓存。
  • 减少用户和服务器的距离(cdn)

http缓存(面试重点)

http请求流程

更多详情请见本公众号文章《http网络编程》

  • 客户端发送请求
  • 浏览器获取index.js
  • 此时前后端有一个协商的协议 是get还是post 请求什么文件index.js http/1.1
代码语言:javascript
复制
协议头:值
协议头:值
  • 服务器给你发文件
expires过期

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。但在上面我们提到过,cache-control的优先级更高。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当客户端本地时间被修改以后,服务器与客户端时间偏差变大以后,就会导致缓存混乱。于是发展出了Cache-Control。

Cache-Control:通过相应头询问http有无过期。

Cache control 浏览器没有,服务器给你文件。它是一个相对时间,例如Cache-Control:3600,代表着资源的有效期是3600秒。由于是相对时间,并且都是与客户端时间比较,所以服务器与客户端时间偏差也不会导致问题。Cache-Control与Expires可以在服务端配置同时启用或者启用任意一个,同时启用的时候Cache-Control优先级高。

max-age 指定一个时间长度,在这个时间段内缓存是有效的,单位是s。例如设置 Cache-Control:max-age=31536000,也就是说缓存有效期为(31536000 / 24 / 60 * 60)天,第一次访问这个资源的时候,服务器端也返回了 Expires 字段,并且过期时间是一年后。在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取。

强缓存(200)

过期时间没到:直接200。

实际上这里是没有发送请求(from cache)。

弱缓存(协商缓存,304)

若未命中强缓存(如果已经过期了),浏览器也不会马上加载。

浏览器会将请求发送至服务器。服务器根据http头信息中的Last-Modify/If-Modify-SinceEtag/If-None-Match来判断是否命中协商缓存。如果命中,则http返回码为304,浏览器从缓存中加载资源。

如果没有修改,则返回304

etarg 指纹

与Last-Modify/If-Modify-Since不同的是,Etag/If-None-Match返回的是一个校验码(ETag: entity tag)。ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化*。ETag值的变更则说明资源状态已经被修改。服务器根据浏览器上发送的If-None-Match值来判断是否命中缓存。

用户操作对缓存的影响

如果都不行,那只能重载了!

大公司怎么上线前端代码?

普通文件上线不久是替换文件么?如果这样,用户可能在时间段内无法请求内容。

怎么上线

解决方案:

代码语言:javascript
复制
<script src="xx.js?_v=1.01" />
<script src="xx.js?_v=文件的哈希值" />

webpack-fis可以计算哈希值。

上线顺序

先上线模版html,导致加载的是旧的js会报错。

如果你的js哈希值变了。那么就会放弃旧的缓存,请求新的js资源。

因此:

  • html或者说模板,用不缓存。
  • js长期缓存。

性能优化实践

对于自己的代码,怎么优化?

我们打开一个过往项目,执行npm run build:

会发现gz压缩后的大小已经小了很多。但还是很大。如何去分析自己的项目?

分析工具:webpack-bundle-analyzer

vue ui可以看自己的项目。

代码语言:javascript
复制
npm i webpack-bundle-analyzer -s

然后在webpack 配置里写:

代码语言:javascript
复制
const BundleAnalyzer=require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.export={

  //...

  plugins:[
    new BundleAnalyzer(),
    // ...
  ]
}

在开发环境中看http://localhost:8888,会发现:

Iview 的样式占了大头。那你就考虑是不是不要全局引入ivew.css了。

路由懒加载:

简单说:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

我们在vue-router里使用类似这样的方式2引入组件:

在react里面也是类似的。

服务端渲染(ssr)

传统的spa应用。首页白屏是非常常见的。对于seo来说也是非常不友好。根治这个问题,解决方案就是ssr。

一个同构应用,需要服务端(server和next两个入口)

vue的服务端渲染

本质就是在服务端把渲染的活给干了·

代码语言:javascript
复制
const Vue = require('vue')
// 创建一个express应用
const server = require('express')()
// 提取出renderer实例
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
  // 编写Vue实例(虚拟DOM节点)
  const app = new Vue({
    data: {
      url: req.url
        },
        // 编写模板HTML的内容
        template: `<div>访问的 URL 是: {{ url }}</div>` })
        // renderToString 是把Vue实例转化为真实DOM的关键方法
      renderer.renderToString(app, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
        }

        // 把渲染出来的真实DOM字符串插入HTML模板中
    res.end(`
      <!DOCTYPE html>
      <html lang="en">
        <head><title>Hello</title></head>
        <body>${html}</body>
      </html>
`) })
})
server.listen(8080)

关于nuxt.js我将在另外一篇文章中阐述。

react开启ssr

关键词:renderToString。属于react-dom的一个api。

对于jsx语法,在服务端必须印图babel。

代码语言:javascript
复制
// 注意这是服务端。
import express from 'express'
import React from 'react'
import { renderToString } from 'react-dom/server'
import App from './App'
const app = express()
// renderToString 是把虚拟DOM转化为真实DOM的关键方法
const RDom = renderToString(<App />)
// 编写HTML模板,插入转化后的真实DOM内容
const Page = `
            <html>
              <head>
                <title>test</title>
              </head>
              <body>
                <span>ssr </span>
                ${RDom}
              </body>
            </html>`
app.get('/index', function(req, res) {
  res.send(Page)
})
// 配置端口号
const server = app.listen(8000)

ssr理论必读。

雅虎军规

不一定都很合适。只能说在不同环境下有不同的配置。

代码语言:javascript
复制
尽量减少 HTTP 请求个数——须权衡
使用 CDN(内容分发网络)
为文件头指定 Expires 或 Cache-Control ,使内容具有缓存性。避免空的 src 和 href
使用 gzip 压缩内容
把 CSS 放到顶部
把 JS 放到底部
避免使用 CSS 表达式
将 CSS 和 JS 放到外部文件中
减少 DNS 查找次数
精简 CSS 和 JS
避免跳转
剔除重复的 JS 和 CSS
配置 ETags
使 AJAX 可缓存
尽早刷新输出缓冲
使用 GET 来完成 AJAX 请求
延迟加载
预加载
减少 DOM 元素个数
根据域名划分页面内容
尽量减少 iframe 的个数
避免 404
减少 Cookie 的大小
使用无 cookie 的域
性能监控

https://developer.mozilla.org/zh-CN/docs/Web/API/Performance

performance

直接在浏览器控制台输入

代码语言:javascript
复制
performance.getEntriesByType('navigation')

我们可以通过这个api来进行一个统计。

比如说,以下两个值之差就是dns寻找时间。

火焰图

重定向耗时:redirectEnd - redirectStart DNS查询耗时 :domainLookupEnd - domainLookupStart TCP链接耗时 :connectEnd - connectStart HTTP请求耗时 :responseEnd - responseStart 解析dom树耗时 : domComplete - domInteractive 白屏时间 :responseStart - navigationStart DOMready时间 :domContentLoadedEventEnd - navigationStart onload时间:loadEventEnd - navigationStart,也即是onload回调函数执行的时间。

lighthouse

lighthouse是一款由谷歌团队开发的一款开源的网站性能测评浏览器扩展程序。

目前测试项包括页面性能PWA可访问性(无障碍)最佳实践SEO。Lighthouse会对各个测试项的结果打分,并给出优化建议,这些打分标准和优化建议可以视为Google的网页最佳实践。

使用后可以看到网站性能。

节流和防抖

这是面试的重点。以班车为例子。

防抖

什么叫防抖?尝试手写一下。

设想你进行百度搜索,业务逻辑是输入每个文字(onkeyup),都弹出搜索建议。

但是这样每次调优都很很痛苦。我还没输入完成你就弹建议。不符合操作习惯。

所以防抖的核心思想是:频繁调用一个请求时,等待最后一个操作结束之后。才进行操作。

代码语言:javascript
复制
<input type="text" id="aaa">
<script>
    const aaa=document.querySelector('#aaa');
  aaa.addEventListener('keyup',(e)=>{
    // 假设这时就发送了请求。
      console.log(`你查找的是不是:${aaa.value}`)
    })
</script>

这段代码缺陷明显。我想输入111,还没输完就累计请求了3次。所以需要防抖一下。

代码语言:javascript
复制
        const aaa = document.querySelector('#aaa');

        // 防抖函数
        const debounce = function(fn, interval){
            if(this.timer){
                clearTimeout(this.timer);
            }
            this.timer = setTimeout(() => {
                fn();
            }, interval)
        }

        aaa.addEventListener('keyup', (e) => {
            debounce(()=>console.log(`你查找的是不是:${aaa.value}`),1000)
        });

最后只执行了一次。timer以闭包的形式存下来了。

节流

与防抖对应的就是节流,也就是说,我每隔一定的时间间隔,发车。

代码语言:javascript
复制
        // 节流
        const throttle=(fn,interval)=>{
            const lastTime=0;
            return function(...args){
                let now=new Date();
                if(now-lastTime>interval){
                    fn.apply(this,args);
                    lastTime=now;
                }
            }
        }

这样就省去了定时器。

防抖和节流在数据校验上很有用。没输完就校验是毫无意义的。

重绘和回流

回流:当你改变一个dom的几何尺寸。浏览器就会重新计算,导致其它标签外观变化。甚至触发蝴蝶效应。

这就是回流。

相反,你只是改了一个小div的颜色,其它属性没有变化,这就是重绘。

回流必定导致重绘,但重绘未必导致节流。

图片懒加载

懒加载通常会用data-url存放你想加载的图片。通过一定的条件触发加载。

长列表

给你1w条数据,怎么显示?

在一个长列表(虚拟列表)中,假设我有1w条,触发dom结构是非常痛苦的。

本质上就是和分页类似。

实际上只渲染可见的(前后2-3屏)。超过这个范围:触发新老节点替换渲染。

框架实现:react-virtualized 可以研究下源码实现。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • dns
  • TCP/IP协议
    • 第一次握手(你在不?)
      • 第二次握手(我在哟)
        • 第三次握手(那我发数据了哟)
        • 优化方向
          • 减少文件体积
            • 减少文件请求次数
            • http缓存(面试重点)
              • http请求流程
                • expires过期
                  • Cache-Control:通过相应头询问http有无过期。
                    • 强缓存(200)
                      • 弱缓存(协商缓存,304)
                        • etarg 指纹
                          • 用户操作对缓存的影响
                          • 大公司怎么上线前端代码?
                            • 怎么上线
                              • 上线顺序
                              • 性能优化实践
                                • 分析工具:webpack-bundle-analyzer
                                  • 路由懒加载:
                                    • 服务端渲染(ssr)
                                      • vue的服务端渲染
                                      • react开启ssr
                                    • 雅虎军规
                                      • 性能监控
                                        • performance
                                        • 火焰图
                                        • lighthouse
                                      • 节流和防抖
                                        • 防抖
                                        • 节流
                                      • 重绘和回流
                                        • 图片懒加载
                                          • 长列表
                                          相关产品与服务
                                          内容分发网络 CDN
                                          内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
                                          领券
                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档