说到https和http的区别,可以说一下https服务器和客户端连接的差异,以及https特定的加密算法协商,甚至可能还要说到对称加密,非对称加密和证书等,篇幅很长,请看我之前单独写的一篇https详解,里面讲的非常详细。

  请求优化

  讲请求优化的之前先来总结下上面说到的js, css文件顺序优化,为了让渲染更快,我们需要把js放到尾部,css放到头部,然后还要注意在书写js的时候尽量减少重排,重绘。书写html,css的时候尽量简洁,不要冗余,目的是为了更快的构建DOM树和CSSOM树。好了下面我们在来说说请求优化,请求优化可以从请求数量和请求时间两方面入手

  减少请求数量减少请求时间webpack优化

  介绍了渲染优化,现在来看看webpack优化,自己平常写demo给团队做培训的时候都是自己手写webpack配置,虽然也就几十行,但每次都能让我巩固webpack的基本配置,下面直接说一下webpack优化手段有哪些

  基础配置优化

  

resolve: {
    extensions: ['.ts', '.tsx', '.js']
}

  这个配置表示webpack会根据extensions去寻找文件后缀名,所以如果我们的项目主要用ts写的话,那我们就可以.tsx和.ts写前面,目的是为了让webpack能够快速解析

  

resolve: {
  alias: {
    Components: path.resolve(__dirname, './src/components')
  }
}

  noParse表示不需要解析的文件,有的文件可能是来自第三方的文件,被 providePlugin引入作为windows上的变量来使用,这样的文件相对比较大,并且已经是被打包过的,所以把这种文件排除在外是很有必要的,配置如下

  

module: {
  noParse: [/proj4\.js/]
}

  某些loader会有这样一个属性,目的是指定loader作用的范围,exclude表示排除某些文件不需要babel-loader处理,loader的作用范围小了,打包速度自然就快了,用babel-loader举一个简单例子

  

{
    test: /\.js$/,
    loader: "babel-loader",
    exclude: path.resolve(__dirname, 'node_modules')
}

  这个配置是一个调试项,不同的配置展示效果不一样,打包大小和打包速度也不一样,比如开发环境下cheap-source-map肯定比source-map快,至于为什么,强烈推荐自己之前写的这一篇讲解devtool的文章:webpack devtools篇讲的非常详细。

  

{
    devtool: 'cheap-source-map'
}

  .eslintignore

  这个虽不是webpack配置但是对打包速度优化还是很有用的,在我的实践中eslint检查对打包的速度影响很大,但是很多情况我们不能没有这个eslint检查,eslint检查如果仅仅在vs里面开启的话,可能不怎么保险。

  因为有可能你vs中的eslint插件突然关闭了或者某些原因vs不能检查了,只能靠webpack构建去帮你拦住错误代码的提交,即使这样还不能确保万无一失,因为你可能某一次提交代码很急没有启动服务,直接盲改提交上去了。这个时候只能通过最后一道屏障给你保护,就是在CI的时候。比如我们也会是在jenkins构建的时候帮你进行eslint检查css 加载层,三道屏障确保了我们最终出的镜像是不会有问题的。

  所以eslint是很重要的,不能删掉,在不能删掉的情况下怎么让检查的时间更少了,我们就可以通过忽略文件,让不必要的文件禁止eslint,只对需要的文件eslint可以很大程度提高打包速度

  loader,plugins优化

  上述说了几个基础配置优化,应该还有其他的基础配置,今后遇到了再继续添加,现在在来讲讲利用某些loader,plugins来提高打包速度的例子

  这个loader就是在第一次打包的时候会缓存打包的结果,在第二次打包的时候就会直接读取缓存的内容,从而提高打包效率。但是也需要合理利用,我们要记住一点你加的每一个loader,plugins都会带来额外的打包时间。这个额外时间比他带来的减少时间多,那么一味的增加这个loader就没意义,所以cache-loader最好用在耗时比较大的loader上,配置如下

  

{
  rules: [
    {
      test: /\.vue$/,
      use: [
        'cache-loader',
        'vue-loader'
      ],
      include: path.resolve(__dirname, './src')
    }
  ]
}

  在上面的渲染优化中我们已经知道,文件越小渲染的速度是越快的。所以我们在配置webpack时候经常会用到压缩,但是压缩也是需要消耗时间的,所以我们我们经常会用到上面三个插件之一来开启并行压缩,减少压缩时间,我们用webpack4推荐使用的terse-webpack-plugin做例子来说明

  

optimization: {
  minimizer: [
      new TerserPlugin({
        parallel: true,
        cache: true
      })
    ],
}

  这几个loader/plugin和上面一样也是开启并行的,只不过是开启并行构建。由于happypack的作者说自己的兴趣已经不再js上了,所以已经没有维护了,并推荐如果使用的是webpack4的话,就去使用thread-loader。基本配置如下

  

     {
        test: /\.js$/,
        use: [
          {
            loader: "thread-loader",
            options: threadLoaderOptions
          },
          "babel-loader",
        ],
        exclude: /node_modules/,
      }

  上面说的几个并行插件理论上是可以增加构建速度,网上很多文章都是这么说的,但是我在实际的过程中使用,发现有时候不仅没提升反而还降低了打包速度,网速查阅给的理由是可能你的电脑核数本来就低,或者当时你CPU运行已经很高了,再去开启多进程导致构建速度降低。

  上面说的几个并行插件可能在某些情况下达不到你想要的效果css 加载层,然而在我们团队优化webpack性能经验来看,这次所说的两个插件是很明显并且每次都能提高打包速度的。原理就是先把第三方依赖先打包一次生成一个js文件,然后真正打包项目代码时候,会根据映射文件直接从打包出来的js文件获取所需要的对象,而不用再去打包第三方文件。只不过这种情况打包配置稍微麻烦点,需要写一个webpack.dll.js。大致如下

  webpack.dll.js

  

const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: {
        library: ["vue", "moment"]
    },

    output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, 'json-dll'),
        library: '[name]'
    },

    plugins: [
        new webpack.DllPlugin({
            path: './json-dll/library.json',
            name: '[name].json'
        })
    ]
}

  webpack.dev.js

  

  new AddAssetHtmlWebpack({
     filepath: path.resolve(__dirname, './json-dll/library.dll.js')
  }),

 new webpack.DllReferencePlugin({
      manifest: require("./json-dll/library.json")
    })

  其他优化配置

  这些插件就简单的介绍下,在我的个人项目中已经使用过,自我感觉还可以,具体使用可以查阅npm或者github

  这个插件可以用可视化帮我们分析打包体积,从而来采用合适的优化方式去改进我们的webpack配置

  这个插件可以告诉我们打包时候每一个loader或者plugin花费了多少时间,从而对耗时比较长的plugin和loader做优化

  这个插件可以帮我们优化打包日志,我们打包时候经常看到很长一个日志信息,有的时候是不需要的,也不会去看所以可以用这个插件来简化

  代码优化

  这是最后一部分代码优化了,这里的代码性能优化我只说我在工作中感受到的,至于其他的比较小的优化点比如createDocumentFragment使用可以查查其他文章

  能不操作dom不要操作dom,哪怕有时候需要改设计

  很多情况下我们都能用css还原设计稿,但是有些时候单单从css没法还原,尤其组件还不是你写的时候,比如我们团队用的就是antd,有时候的产品设计单从css上没法实现,只能动用js,删除,增加节点在配合样式才能完成。

  由于我们又是一个做大数据的公司,这个时候就会出现性能问题,最开始写代码时候,产品说什么就是什么,说什么我都会想办法搞出来,不管用什么方法。后来到客户现场大数据请况下,性能缺点立马暴露的出来。

  所以代码优化的原则之一我认为是能不写的代码就不写,当然这是要从性能角度出发,通过性能分析给产品说出理由,并且最好还能提供更好的解决方案,这个才是我们需要考虑的。

  如果用的是react 一定用写shouldComponentUpdate这个生命周期函数,不然打印的时候你会发现,你自己都迷糊为什么执行了这么多遍

  将复杂的比对,变成简单比对

  这句话是什么意思了?我们就拿shouldComponentUpdate举例子,用这个函数没问题,但是可以做的更好,我们在工作中经常这么写

  

shouldComponentUpdate(nextPrpops) {
  return JSON.stringify(nextPrpops.data) !== JSON.stringify(this.props.data)
}

  如果这是一个分页表格,data是每一页数据,数据改变了重新渲染,在小数据场景下这本身是没有问题。但是如果在大数据的场景下可能会有问题,可能有人有疑问,既然做了分页怎么还会有大数据了,因为我们的产品是做大数据分析日志的,一页十条日志,有的日志可能非常的长,也就是说就算是10条数据比对起来也是很耗时,所以当时想法能不能找到其他的替代变量来表示数据变了?比如下面这样

  

shouldComponentUpdate(nextPrpops) {
  return nextPrpops.data[0].id !== this.props.data[0].id
}

  第一条的id不一样就表示数据变化了行不行,显然在某种情况下是存在的,也有人会说可能会出现id一样,那如果换成下面这种了?

  

shouldComponentUpdate(nextPrpops) {
  return nextPrpops.current !== this.props.current
}

  将data的比对转换成了current的比对,因为页数变了,数据基本都是变了,对于我们自己日志的展示来说基本不存在两页数据是一模一样的,如果有那可能是后台问题。然后在好好思考这个问题,即使存在了两页数据一摸一样,顶多就是这个表格不重新渲染,但是两页数据一摸一样不重新渲染是不是也没有问题,因为数据是一样的。或者如果你还是不放心,那下面这种会不会好点

  

this.setState({
  data,
  requestId: guid()
})

shouldComponentUpdate(nextPrpops) {
  return nextPrpops.requestId !== this.props.requestId
}

  给一个requestId跟宗data,后面就只比对requestId。上面的写法可能都有问题,但是主要是想说的是我们在写代码时候可以想想是不是可以"将复杂的比对,变成简单比对"

  学习数据结构和算法,一定会在你的工作中派上用场

  我们经常会听到学习数据结构和算法没有什么大的用处,因为工作基本用不上。这句话我之前觉得没错,现在看来错的很严重。我们所学的每一样技能,都会在将来的人生中派上用场。之前写完代码就丢了不去优化所以我觉得算法没意义,又难又容易忘记。但现在要求自己做完需求,开启mock,打开perfermance进行大数据量的测试,看着那些标红的火焰图和肉眼可见的卡顿,就明白了算法和数据结构的重要性,因为此时你只能从它身上获取优化,平时你很排斥它,到优化的时候你是那么想拥有它。我拿自己之前写的代码举例,由于公司代码是保密的我就把变量换一下,伪代码如下

  优化手段有哪些基础配置优化的配置顺序优化?

  

data.filter(({id}) => {
  return selectedIds.includes(id);
})

  就是这样几行代码,逻辑就是筛选出data里面已经被勾选的数据。基本上很多人都可能这么写,因为我看我们团队里面都是这么写的。产品当时已经限制data最多200数据,所以写完完全没压力,性能没影响。但是秉着对性能优化的原则(主要是被现场环境搞怕了~~~),我开启了mock服务,将数据调到了2万条再去测试,代码弊端就暴露出来了,界面进入卡顿,重新选择的时候也会卡顿。然后就开始了优化,当时具体的思路如下

  按照现在的代码来看,这是一个两层循环的暴力搜索时间复杂度为O(n^2)。所以想着能不能降一下复杂度至少是O(nlogn),看了一下代码只能从selectedIds.includes(id)这句入手,于是想着可不可以用二分,但是立马被否定因为二分是需要有序的,我这数组都是字符串怎么二分。

  安静了一下之后,回想起看过的算法课程和书籍以及做的算法题,改变暴力搜索的方法基本都是

  1:上指针

  2:数组升维

  3:利用hash表

  前两者被我否定了因为我觉得还没那么复杂,于是利用hash表思想解决这个问题,因为js里面有一个天然的hash表结构就是对象。我们知道hash表的查询是O(1)的,所以我将代码改写如下

  

const ids = {};
selectedIds.forEach(id => ids[id] = 1);

data.filter(({id}) => {
  return !!ids[id];
})

  将从selectedIds查询变成从ids查询,这样时间复杂度就从O(n^2)变成了O(n)了,这段代码增加了

  

const ids = {};
selectedIds.forEach(id => ids[id] = 1);

  其实增加了一个selectedIds遍历也是一个O(n)的复杂度,总来说复杂度是O(2n),但是从时间复杂度长期期望来看还是一个O(n)的时间复杂度,只不过额外增加了一个对象,所以这也是一个典型的空间换时间的例子,但是也不要担心,ids用完之后垃圾回收机制会把他回收的。

  最后

  其实这篇文章写出来还是对自己帮助很大,让自己系统的梳理了一下自己理解的前端优化,希望对你们也有帮助。

TAGS:代码优化 大数据 css 加载微软雅黑字体 eslint css 加载层 css实现加载动画 webpack
!如链接失效请在下方留言。本站所有资源均来源于网络,版权属于原作者!仅供学习参考,本站不对您的使用负任何责任。如果有侵权之处请第一时间联系我们删除,敬请谅解!