前端优化的各种手段

3,015 阅读4分钟

从度娘到处搜刮的一些前端优化的手段,主要是一些对首屏加载的优化已经减小打包文件大小的措施。

目录

优化首屏加载体验

在js等文件尚未加载完成之前在页面展示一些信息

  • 在入口 index.html 中加入骨架屏或者loading动画或者可跳转的导航栏,可以消除白屏 争取更多加载时间
  • 框架中的渲染机制是在js、css等资源加载完成之后再渲染App到根节点上,在 index.html 中加入的HTML代码是不对被打包处理的,会在浏览器请求页面之后直接显示在浏览器中

路由使用懒加载

  • 当定位到相应路由时才加载组件页面,极大缩短了资源加载时间,优化了 首屏加载 过慢的问题
  • vue:比较简单,直接在创建路由的时候使用const XXX = () => import('./XXX.vue') 引入组件,再放入路由配置选项component: XXX
    • 注意:必须先将组件引入变量中再放入component,直接使用以下方式无效果。component: () => import('./XXX.vue')
  • react
    //lazyRouter.jsx
    import React from 'react'
    
    function LazyRouter(componentfn) {
        class LazyloadComponent extends React.Component {
            constructor(props) {
                super(props);
                this.state = {
                    component: null
                }
            }
            async componentDidMount() { //注意参考文档中使用的componentWillMount将被废除
                const { default: component } = await componentfn();
                this.setState({ component })
            }
            render() {
                const C = this.state.component;
                return C ? <C {...this.props} /> : null;
            }
        }
        return LazyloadComponent
    }
    export default LazyRouter
    
    //使用
    const Home = LazyRouter(() => import('./pages/Home'))
    //...
    <Route path="/home" component={Home}>
    
    • lazyload-loader 插件
        module: {
        rules: [
        {
            test: /\.(js|jsx)$/,,
            use: [
            'babel-loader',
            'lazyload-loader' //注意位置
            ]
        }]
        }
        //......
        // 使用lazy! 前缀 代表需要懒加载的Router
        
        import Shop from 'lazy!./src/view/Shop';
        
        // Router 正常使用
        <Route path="/shop" component={Shop} />
    

减小webpack打包后的文件大小

图片压缩

  • 大尺寸图片大小尽量控制在 250kb 以下 小尺寸图片尽量控制在 50kb 以下
  • 在线压缩工具
  • 对于那种小图标 尽量使用 iconfont 代替图片

字体包压缩

  • 字体包通常很大 但是项目文件中使用该字体的往往就几个字
  • 安装 font-spider 可以把需要的字符抽取出来生成字体文件
    • npm install font-spider -g
    • 新建一个文件夹用于生成新的字体文件
    • 将需要使用字体的字符写入html任意标签中 并且引用原字体文件
    • font-spider ./demo/*.html 使用命令解析这个html文件 就会生成新的字体包

防止编译文件中出现map文件

  • 打包后产生后缀名为.map的文件是由于配置了sourcemap选项生成的,打包后的文件不容易找到出bug对应的源代码的位置,sourcemap就是来帮我们解决这个问题的,有了map就可以像未压缩的代码一样,准确的输出是哪一行哪一列有错。
  • 去config/index.js中改一个参数就行 productionSourceMap: false

使用CDN

  • 解决 打包时间长、打包后的体积过大、服务器网络不稳定或者宽带不高引起的页面加载过慢的问题
  • 在 index.html 引入相应的cdn路径,在 webpack.base.conf.js 配置 需要避免被打包的依赖
externals: {
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'element-ui': 'ELEMENT',
    'echarts': 'echarts',
    'vuex': 'Vuex'
},

清扫代码

  • 使用Webpack的UglifyJsPlugin插件,压缩代码、删除console.log等调试语句、删除单行/多行/文档注释、删除sourceMap、copyright等
  • 在 webpack.prod.conf.js 中设置
new UglifyJsPlugin({
    uglifyOptions: {
        comments: false,
        show_copyright: false,
        compress: {
            warnings: false,
            drop_debugger: true,
            drop_console: true
        }
    },
    sourceMap: false,
    parallel: true
}),

使用gzip压缩

  • 去config/index.js中改一个参数 productionGzip: true
  • 安装 npm install --save-dev compression-webpack-plugin@1.1.11
  • 再打包就会生成 .gz 的压缩包文件(需服务器配合使用)

UI框架

  • 必须按需加载 无法按需加载的采用所需功能的替代方案

momentJS

  • moment带有很多的语言包 将中文包过滤出来 在 webpack.prod.conf.js 的 plugins中加入 new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/), 大约会减少200kb
  • 如果只用了moment的极少功能 如 format() 可以自己实现简易版的函数代替
// 简易版moment代替moment.js
class Moment {
    date
  constructor(arg = new Date().getTime()) {
    this.date = new Date(arg);
  }
  padStart(num) {
    num = String(num);
    if (num.length < 2) {
      return '0' + num;
    } else {
      return num;
    }
  }
  unix() {
    return Math.round(this.date.getTime() / 1000);
  }
  static unix(timestamp) {
    return new Moment(timestamp * 1000);
  }
  format(formatStr) {
    const date = this.date;
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    const week = date.getDay();
    const hour = date.getHours();
    const minute = date.getMinutes();
    const second = date.getSeconds();
    const weeks = ['一', '二', '三', '四', '五', '六', '日'];

    return formatStr.replace(/Y{2,4}|M{1,2}|D{1,2}|d{1,4}|h{1,2}|m{1,2}|s{1,2}/g, (match) => {
      switch (match) {
        case 'YY':
          return String(year).slice(-2);
        case 'YYY':
        case 'YYYY':
          return String(year);
        case 'M':
          return String(month);
        case 'MM':
          return this.padStart(month);
        case 'D':
          return String(day);
        case 'DD':
          return this.padStart(day);
        case 'd':
          return String(week);
        case 'dd':
          return weeks[week];
        case 'ddd':
          return '周' + weeks[week];
        case 'dddd':
          return '星期' + weeks[week];
        case 'h':
          return String(hour);
        case 'hh':
          return this.padStart(hour);
        case 'm':
          return String(minute);
        case 'mm':
          return this.padStart(minute);
        case 's':
          return String(second);
        case 'ss':
          return this.padStart(second);
        default:
          return match;
      }
    });
  }
}

export const moment = (arg) => {
  return new Moment(arg);
};

echarts 压缩

  • 仅保留自己项目中需要的控件 能大幅缩减包大小
  • 在线定制

主流框架性能优化

使用 immutable.js 优化深层树渲染 避免不必要的渲染

  • 参考
  • 关键是对 shouldComponentUpdate 的控制
  • diff是比较虚拟节点树 对不同的节点进行更新再整体覆盖真实节点执行render函数 虽然避免了大量的DOM操作但是要渲染整个节点树 而immutable对象因为是不可变的 当顶层render执行时 那些没有改变的节点就不会触发他们自身的render函数 从而大幅提升性能
  • React.PureComponent 在只有一层 state 和 props 时 会自动进行浅比较 从而控制shouldComponentUpdate
  • 深层 state 和 props 就要用到 immutable.js 如果对象树中一个节点发生变化 只修改这个节点和受它影响的父节点,其它节点则进行共享
import { is } from 'immutable';

shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
  const thisProps = this.props || {}, thisState = this.state || {};

  if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
      Object.keys(thisState).length !== Object.keys(nextState).length) {
    return true;
  }

  for (const key in nextProps) {
    if (!is(thisProps[key], nextProps[key])) {
      return true;
    }
  }

  for (const key in nextState) {
    if (thisState[key] !== nextState[key] && !is(thisState[key], nextState[key])) {
      return true;
    }
  }
  return false;
}

其他手段

  • 使用 webpack 模块打包器 从多方面进行优化
  • 尽可能减少请求次数 避免不必要的重复的请求
  • 预加载即将呈现的内容 推迟加载当前不需要的内容
  • 减少 DOM元素数量 document.getElementsByTagName('*').length 可获取页面元素数量
  • CSS 避免使用 @import
  • 移动端 避免空的图像来源