项目性能优化

43 阅读7分钟

一 懒加载

懒加载的概念

  1. 懒加载也叫做延迟加载啊、按需加载,可以在网页中对长列表数据或者图片进行延迟加载,是一种较好的网页性能优化的方式。

懒加载的概念懒加载的特点

  1. 减少无用的资源加载
  2. 提升用户体验
  3. 防止加载过多的图片或者数据影响其他资源文件的加载

懒加载于预加载的区别

  1. 懒加载也叫做预加载,指的是长网页中延迟加载图片的时机,当用户需要访问时,再去加载
  2. 预加载指的是将所需的资源提前请求加载到本地,这样后面在需要的时候就直接从缓存中拿取

二 回流与重绘

回流

  1. 当渲染树中部分或者全部元素的尺寸、结构属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就时回流

    1. 页面的首次渲染
    2. 浏览器窗口大小发生变化
    3. 元素的内容发生变化
    4. 元素的尺寸或者位置发生变化
    5. 元素的字体大小发生变化
    6. 激活css伪类
    7. 查询某些属性或者调用某些方法
    8. 添加或删除可见的DOMN元素
  2. 因为浏览器的渲染时基于流式布局的,它的影响范围分为

    1. 全局范围:从根节点开始,对整个渲染树进行重新布局
    2. 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局

重绘

  1. 当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘 这些操作会导致重绘:
color、background 相关属性: background-color、background-image等
outline 相关属性: outline-color、outline-width 、text-decoration
border-radius,isibility、 box-shadow

注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流

减少回流与重绘的措施

  1. 操作DOM时,尽量在低层级的DOM节点进行操作
  2. 不要使用 table 布局,一个小的改动可能会使整个 table 进行重新布局
  3. 使用CSS的表达式
  4. 不要频整操作元素的样式,对于静态页面,可以修改类名,而不是样式
  5. 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素避免频繁操作DOM,可以创建一个文档片段 documentFragment ,在它上面应用所有DOM操作,最后再把它添加到文档中
  6. 将元素先设置 display: none ,操作结束后再把它显示出来。因为在display厘性为none的元素上进行的DOM操作不会引发回流和重绘。
  7. 将DOM的多人读操作(或者写操作) 放在一起,而不是读写操作穿插着写,这得益于浏览器的染队列机制。

浏览器针对页面的回流与重绘,进行了自身的优化一一渲染队列

浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。

上面,将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,原本应该是触发多次回流,变成了只触发一次回流。

三 防抖和节流

节流和防抖的意思

  1. 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
  2. 函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一人单位时间内某事性被触发多次,只有一次能生效。节流可以使用在 scrol 函数的事件监听上,通过事件节流来降低事件调用的频率。

节流和防抖的实现

// 防抖函数
function db(fn, time) {
    let timer = null;
    return function(){
        let that = this,
        arg = arguments;
        if(timer){
            clearTimeout(timer);
            timer=null;
        }
        timer = setTimeout( ()=>{
            fn.apply(that,arg);
        },time)
    }
}
// 节流函数
function thro( fn, delay){
    let date = Date.now();
    return function(){
        let that = this,
        arg = arguments,
        nowTime = Date.now();
        if(nowTime - date >= delay){
            date = Date.now();
            return fn.apply(that,arg);
        }

    }
}

四 webpack优化

webpack打包速度

(1)优化 Loader

对于Loader 来说,影响打包效率首当其冲必 Babel了。因为 Babel 会将代码转为字符串生成 AST,然后对AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。当然了,这是可以优化的 首先我们优化 Loader 的文件搜索范围

module.exports = {
    module: {
        rules: [
            {
                // js 文件才使用 
                babeltest: /\.js$/,
                loader: 'babel-loader',
                // 只在 src 文件夹下查找
                include: [resolve('src')],
                // 不会去查找的路径
                exclude: /node_modules/,
                // 将 abel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以
                // 大幅度加快打包时间
                loader: "babel-loader?cacheDirectory=true"
            }
        ]
    }
}

(2) HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Lader 的时候,长时间编译的任务很多,这样就会导致等待的情况。

HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了

 module: {
        loaders: [
            {
                test: /\js$/,
                include: [resolve('src')],
                exclude: /node_modules/,// id 后面的内容对应下面
                loader: "happypack/loader?id=happybabel",
            }
        ]
    },
    plugins: [
        new HappyPack({
            id: "happybabel",
            loaders: ["babel-loader?cacheDirectory"],
            // 开启 4 个线程
            threads: 4
        })
    ]
(3)DlIPlugin

DlIPlugin 可以将特定的类库提前打包然后引入,这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。 DllPlugin 的使用方法如下:

// 单独配置在一个文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require("webpack")
module.exports = {
    entry: {
        // 想统一打包的类库
        vendor: ['react']
    },
    output: {
        path: path.join(dirname, "dist"),
        filename: '[name].dll.js',
        library: "[name] -[hash]"
    },
    plugins: [
        new webpack.DllPlugin({
            // name 必须和 output.library 一致
            name: "[name]-[hash]",// 该属性需要与 DIIReferenceplugin 中一致
            context: __dirname,
            path: path.join(__dirname, 'dist', "[name]-manifest.json")
        })]
}

然后需要执行这个配置文件生成依赖文件,接下来需要使用 DllReferencePlugin 将依赖文件引入项目中

// webpack.conf.js
module.exports = {
    // ...省略其他配置
    plugins: [
        new webpack.DllReferenceplugin({
            context: dirname,
            // manifest 就是之前打包出来的 json 文件
            manifest: require("./dist/vendor-manifest.json")
        })]
}
(4) 代码压缩

在webpack3中需要使用 UglifyJS 来压缩代码,但是在webpack4中只需要将 mode 设置为 production 就可以默认开启。

(5) 其他的方法

可以通过一些小的优化点来加快打包速度

  1. resolve.extensions : 用来表明文件后缀列表,默认查找顺序是 [".js",".json"],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面
  2. resolve.alias : 可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径 3.** module.noParse :** 如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件这种方式对于大型的类库很有帮助

如何使用 webpack优化前端性能

用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效

  1. 压缩代码: 删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的 UglifyJsPluginParallelUglifyPlugin 来压缩JS文件,利用 cssnano (css-loader?minimize) 来压缩css
  2. 利用CDN加速: 在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output 参数和各loader的 publicPath 参数来修改资源路径
  3. Tree Shaking: 将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现
  4. Code Spliting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利用浏览器缓存提取公共第三方库: SplitChunksplugin 插件来进行公共模块抽取,利用浏览器缓存可以长期缓存这些无需频繁变动的公共代码

如何提高 webpack的构建速度

  1. 多入口情况下,使用 CommonsChunkPlugin 来提取公共代码
  2. 利用 DIPluginDIReferencePluain 预编译资源模块 通过 DlPlugin 来对那些我们引用是绝对不会修改的npm包来进行预编译,再通过 DIIReferencePlugin 将预编译的模块加载进来
  3. 通过 externals 配置来提取常用库
  4. 使用 Happypack 实现多线程加速编译
  5. 使用 Tree-shakingScope Hoisting 来剔除多余代码
  6. 使用 webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度