# 1、雅虎军规

# 1、图片优化

  • 图片压缩(https://tinypng.com/)
  • 雪碧图(Sprites)
    • 把图片横向合并而不是纵向,横向更小。
    • 把颜色近似的图片合并到一张雪碧图,这样可以让颜色数更少,如果低于 256 就可以用 png8
    • 并且合并时图片间的间距不要太大。这对图片大小影响不是太大,但客户端解压时需要的内存更少。
  • 禁止缩放图片在HTML文件当中
  • 使 favicon 小且缓存

# 2、css优化

  • 将css内联样式放在顶部(<head>标签)。   把样式表移到 <head> 里会让页面更快。这是因为把样式表移到 <head> 里允许页面逐步渲染。   把样式表放在文档底部的问题是它阻止了许多浏览器的逐步渲染,包括 IE。这些浏览器阻止渲染来避免在样式更改时需要重绘页面元素。所以用户会卡在白屏。
  • 避免CSS表达式(会导致频繁计算)
  • 选择<link>而不是@import(用 @import 和把 CSS 放到页面底部行为一致)。
  • 避免使用(IE)过滤器。   这个过滤器的问题是它阻止了渲染,并在图片下载时冻结了浏览器。另外它还引起内存消耗,并且它被应用到每个元素而不是每个图片,所以问题的严重性翻倍了。

# 3、cookie

  • 减少cookie的体积
    • 消除不必要的 cookie
    • 尽可能减小 cookie 的大小来降低响应时间。
    • 注意设置 cookie 到合适的域名级别,则其它子域名不会被影响。
    • 正确设置 Expires 日期。早一点的 Expires 日期或者没有尽早的删除 cookie,优化响应时间。
  • 对组件使用无Cookie域。   启用一个全新的域名来托管静态组件,因为cookie是可以跨二级域名的。

# 4、服务端

  • CDN
  • Expires 或者 Cache-Control 头部
    • 对静态组件:通过设置 Expires 头部来实现“永不过期”策略。
    • 对动态组件:用合适的 Cache-Control 头部来帮助浏览器进行有条件请求。
  • 开启Gzip压缩。   html,脚本,样式,xml 和json 等等都应该被gzip,而图片,pdf等等不应该被gzip,因为它们本身已被压缩过,gzip 它们只是浪费 cpu,甚至增加文件大小。
  • ETags 配置
  • 早一点刷新buffer   当用户请求一个页面,服务器一般要花 200-500ms 来拼凑整个页面。这段时间,浏览器是空闲的(等数据返回)。这时可以将部分准备好的 html 响应给浏览器。
  • ajax请求用get(get仅用一个 TCP包发送)。
  • 避免空src的图片   因为空src也会发出请求,发送大量的意料之外的流量,会削弱服务器。

# 5、JavaScript

  • 把脚本放到底部
  • 使用外部JS和CSS   使用外部文件一般会加快页面,因为 JS 和 CSS 文件被浏览器缓存了。
  • 压缩JS和CSS
  • 删除重复的脚本
  • 最小化DOM访问(用 JS 访问 DOM 元素是缓慢的)
    • 缓存访问过的元素的引用
    • 在 DOM 树外更新节点,然后添加到 DOM 树
    • 避免用 JS 实现固定布局
  • 开发聪明的事件处理
    • 事件委托
    • 在DOMContentLoaded回调中处理DOM树,而不是 onload回调。

# 6、Mobile

  • 保持组件小于25K(这个限制与 iPhone 不缓存大于 25K 的组件相关。)
  • 组件打包到一个多部分文档(通过一个 HTTP 请求获取多个组件)

# 7、Content

  • 最小化 http 请求
    • Combined files (合并文件)
    • CSS Sprites(雪碧图可以合并多个背景图片,通过 background-image 和 background-position 来显示不同部分。)
    • Image maps (雪碧图)
    • Inline images (内联图片)使用 data:url scheme 来内联图片,将内嵌图像组合到(缓存的)样式表中同样也是一种减少 HTTP 请求并避免增加页面大小的方法。
  • 减少DNS查询(避免 DNS 查找减少响应时间)
  • 避免重定向。(301 302响应一般不会被缓存,除非有额外的头部信息,比如 Expires 或 Cache-Control 指定要缓存,同时重定向会降低用户体验)
  • 让Ajax可缓存
  • 延迟加载组件(js,css,html)
  • 预加载组件(利用浏览器的空闲时间来请求你将来会用到的组件)
  • 减少dom数
  • 把组件分散到不同的域名(把组件分散到不同的域名允许你最大化并行下载数)
  • 最小化iframe的数量。 <iframe>优点:
    • 帮助解决缓慢的第三方内容的加载,如广告和徽章 安全沙盒
    • 并行下载脚本 <iframe>缺点:
    • 即使空的也消耗(资源和时间)
    • 阻塞了页面的onload
    • 非语义化(标签)
  • 不要404   当链接指向外部 js 但却得到 404 结果。这样首先会降低(占用)并行下载数,其次浏览器可能会把 404 响应体当作 js 来解析,试图从里面找出可用的东西。

# 2、性能优化指标

# 1、 Performance API

  • memory
  • navigation
  • onresourcetimingbufferfull
  • timeOrigin
  • timing(重点关注对象)

# 2、感官性能优化指标

  • First Paint(简称FP):表示文档中任一元素首次渲染时间。
  • First Contentful Paint(简称FCP):当浏览器首次渲染任何文本,图像(包括背景图像),非白色画布或SVG时。这个指标就是我们日常说的白屏时间。
  • First Meaningful Paint(简称FMP):首次有意义的绘制,这个指标反映的是主要内容出现在页面上所需要的时间,如果FMP时间过长的话,这里就要考虑是不是静态文件阻塞了主线程。
  • Time To Interactive(TTI):可交互时间,等到服务器通过HTTP协议将响应全部返回之后,便开始DOM Tree 的构建,完成之后,网页变成可交互状态,到此为止便是网页的可交互时间。性能优化重点

# 3、Lighthouse 更直观的性能优化工具。

# 3、网络部分

# 1、DNS Prefetch

  • DNS Prefetch原理 浏览器会在空闲时间提前将这些域名转化为对应的 IP地址,这里为了防止 DNS Prefetch 阻塞页面渲染影响用户体验,Chrome 浏览器的引擎并没有使用它的网络堆栈去进行预解析,而是单独开了8个完全异步的Worker 线程专门负责DNS PrefetchDNS Prefetch 解析后的域名对应关系会缓存到本地,一般本地缓存的数量为50~200个
  • 如何使用
    • 浏览器自动解析。浏览器引擎在解析HTML页面的时候,会自动获取当前页面所有的a标签herf属性当中的域名,然后进行DNS Prefetch。
    <meta http-equiv="x-dns-prefetch-control" content="on">
    
    1
    • 手动解析
    <link rel="dns-prefetch" href="//www.imooc.com">
    
    1
  • 使用场景
    • 首页使用 1、基于站点用到的大多数域名都会在首页当中出现。 2、我们可以把其他子页面用到的比较频繁的域名也可以放到首页进行预解析。(使用比较频繁的域名这里一般是指存放图片资源的服务器域名和一些经常发送Ajax请求会用到的域名。)
    • 新用户首次访问 注意: DNS Prefetch 的数量一般是3-5个,因为 DNS Prefetch也是会占用设备宽带的。

# 4、webpack

# 1、构建速度优化(开发环境)

  • npm install 过程中的优化(增加版本描述文件) npm install最耗时部分是递归分析包的依赖关系和版本。 npm install 的时候会经历如下阶段:
    • 确定包的版本和分析包的依赖关系,通过版本描述文件(package-lock.json或者yarn.lock)来读取包的下载地址和版本的相关关系。
    • npm 获取包的下载地址,根据版本描述文件找那个的resolves字段记录的地址来下载文件。
    • 根据npm的缓存机制,如果本地有缓存信息,就直接使用缓存下载。
    • 将下载的压缩包拷贝值本地的node_modules文件夹当中。

# 2、具体仓库地址的选择

1、npm config set registry https://registry.npm.taobao.org
2、也可以使用 nrm(npm i nrm -g) 工具来切换npm仓库
1
2

# 3、提升webpack构建速度

  • 区分生产环境和开发环境
  • 减少不必要的编译
  • 使用模块热更新(HMR)
  • 代码分隔优化(optimization.splitChunks)
optimization: {
    splitChunks: {
        //设置那些代码用于分割
        chunks: "all",
        // 指定最小共享模块数(与CommonsChunkPlugin的minChunks类似)
        minChunks: 1,
        // 形成一个新代码块最小的体积
        minSize: 0,
        //需要被抽离的模块
        cacheGroups: {
            framework: {
                test: /react|lodash/,
                name: "vendor",
                enforce: true
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 配置动态链接库
    • 创建配置文件
    //配置文件webpack.vendor.config.js
    const path    = require('path');
    const webpack = require('webpack');
    module.exports = {
        entry: {
                //提取的公共文件
            vendor: ['react','lodash',] 
        },
        output: {
            path: path.resolve('./dist'),
            filename: 'vendor.js',
            library: '[name]_library'
        },
        plugins: [
            new webpack.DllPlugin({
            path: path.resolve('./dist', '[name]-manifest.json'),
            name: '[name]_library'
            })
        ]
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    • 打包动态链接库并生成 vendor清单。生成 vendrr.js文件和资源清单manifest.json
    • 将 ·vendor连接到项目中,在vue.config.js配置 DllReferencePlugin 来获取刚刚打包的模块清单。
    configureWebpack: config =>{
        if (process.env.NODE_ENV === 'production') {
            // config.plugins.push(new BundleAnalyzerPlugin());
            config.plugins.push(
                new webpack.DllReferencePlugin({
                    // 指定需要用到的 manifest 文件
                        manifest: path.resolve(__dirname, 'dist/manifest.json'), 
                }),
            )
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    • 在首页 index.html 引入我们打包好的vendor.js

# 4、打包质量优化

  • js代码压缩
const os = require('os');
    const UglifyJsParallelPlugin = require('webpack-uglify-parallel');

    new UglifyJsParallelPlugin({
      //开启多进程
      workers: os.cpus().length,
      mangle: true,
      compressor: {
        //忽略警告
        warnings: false,
        //打开console
        drop_console: true,
        //打开debugger
        drop_debugger: true
       }
    })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • css代码压缩
onst OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  module: {
    rules: [
      {
    		test: /\.css$/,
    		use: [
          MiniCssExtractPlugin.loader,   // 分离css代码
          'css-loader',
    		],
			},
    ],
  },
  plugins:[
    new MiniCssExtractPlugin({
        filename: 'static/css/[name].[contenthash:8].css' //提取css存放目录
    }),
    new OptimizeCssAssetsPlugin()  // 使用OptimizeCssAssetsPlugin对CSS进行压缩
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 图片压缩
const ImageminPlugin = require('imagemin-webpack-plugin').default;
module.exports = {
  plugins: [
      new ImageminPlugin({
        pngquant: {
          //指定压缩后的图片质量
          quality: '95-100'
        }
      })
	]
}
1
2
3
4
5
6
7
8
9
10
11
  • 使用Prepack
const PrepackWebpackPlugin = require('prepack-webpack-plugin').default;

module.exports = {
  plugins:[
     new PrepackWebpackPlugin()
  ]
}
1
2
3
4
5
6
7

# 5、图片优化

# 1、图片渐进显示

  • 非代码方式(Progressive JPEG),可以直接使用Photoshop,然后在保存为 JPEG 格式的时候,将连续这个选项勾选即可。
  • 代码方式(在图片出现在视图中加载图片) Element.getBoundingClientRect返回一个对象,提供当前元素节点的大小、位置等信息。
    • x:元素左上角相对于视口的横坐标
    • y:元素左上角相对于视口的纵坐标
    • height:元素高度
    • width:元素宽度
    • left:元素左上角相对于视口的横坐标,与x属性相等
    • right:元素右边界相对于视口的横坐标(等于x + width)
    • top:元素顶部相对于视口的纵坐标,与y属性相等
    • bottom:元素底部相对于视口的纵坐标(等于y + height)
<div class="progressive">
  //src是img本身属性,data-src是自定义属性,相应事件触发后用data-src的属性值代替src属性值
  data-src通过el.dataset.src获取
  <img class="preview lazy" data-src="origin.png" src="preview.png" /> 
</div>
1
2
3
4
5

1

# 2、图片懒加载

  • Element.getBoundingClientRect
//图片什么时候出现在可视区域内
  function isInSight(el) {
    const rect = el.getBoundingClientRect();
        //这里我们只考虑向下滚动
    const clientHeight = window.innerHeight; 
        // 这里加50为了让其提前加载
    return rect.top <= clientHeight + 50;
  }
  //data-src的属性值代替src属性值
  function loadImg(el) {
    if (!el.src) {
      const source = el.dataset.src;
      el.src = source;
    }
  }
  let index = 0;
  function checkImgs() {
    const imgs = document.querySelectorAll('.my-pic');
    for (let i = index; i < imgs.length; i++) {
      if (isInSight(imgs[i])) {
      loadImg(imgs[i]);
        index = i;
    }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • IntersectionObserver
    const io = new IntersectionObserver(entries => {
        entries.forEach(item => {
            if (item.isIntersecting) {
                io.unobserve(item.target); // 停止观察当前元素 避免不可见时候再次调用callback函数
            } 
        });
    });
    const gridItems = document.querySelectorAll('.my-pic');
    gridItems.forEach(item => {
         io.observe(item);
    });
1
2
3
4
5
6
7
8
9
10
11

# 6、缓存

# 1、缓存的优点

  • 获得更快的读写能力
  • 降低数据库压力
  • 减少冗余数据传输(只请求改动的数据)
  • 节省流量
  • 降低延时

# 2、缓存的分类

  • CDN缓存(进行缓存的只有GET请求,CDN 仅仅加速静态站点中的资源)
  • 数据库缓存。 数据库缓存就是我们把一些经常会被访问到资源直接放到内存当中,当数据没有变化我们并不会去让直接读写数据库,只有数据发生变化的时候我们才会去操作数据库。
  • 浏览器缓存(Expire,Cache-control,Last-modified,Etag等)
  • 本地缓存(LocalStorage、SessionStorage和Cookie、WebSql(废弃)和IndexDB)
  • 接口缓存 (将一个方法的返回结果直接缓存起来。这样在调用这个方法的入参相同时,就会直接从缓存中读取相应的结果)
  • Web应用层缓存
Last Updated: 8/19/2020, 11:38:52 PM