关于webpack:代理模式在vue中惰性加载echarts等第三方库

1.根底知识点

(1)代理模式

先从一个简略的例子动手代理模式。如代码所示

// 留神:如果在node环境下运行程序请装置node-fetch
// const fetch=require('node-fetch') 
// 依据keyword申请数据
function request(keyword=''){    
    return new Promise((resolve,reject)=>{
        fetch(`http://example.com/data/${keyword}`)
        .then(response=> {
            resolve(response.json())
        })
    })
}
// 主体程序
async function main(){
    let res
    try{
        res=await request('a')
    }catch(e){

    }
    ..........
}

通常遇到异步申请的场景中,咱们会把异步申请逻辑写在一个独立的函数外面,如上述代码中的request函数,而后主函数main通过调用request函数获取数据。

但如果该接口的开销比拟大、调用比拟频繁多且查问的条件局部反复,咱们能够在main和request两头加一层缓存代理,在下一次调用时如果传入的参数跟之前统一,则可间接返回之前查问的后果。代码如下所示:

// 依据keyword申请数据
function request(keyword=''){    
    return new Promise((resolve,reject)=>{
        fetch(`http://example.com/data/${keyword}`)
        .then(response=> {
            resolve(response.json())
        })
    })
}

// 缓存代理
const requestProxy=(function(){
    let cache={}
    return async function(keyword){
        if(cache[keyword])
            return cache[keyword]
        else{
            cache[keyword]=await request(keyword)
            return cache[keyword]
        }    
    }
})()

// 主体程序
async function main(){
    let res
    try{
        // 第一次:通过异步获取后果
        res=await requestProxy('a')
        // 第二次:通过缓存获取后果
        res=await requestProxy('a')    }catch(e){

    }
    ..........
}

除此以外,代理模式的使用场景也很多,例如在img节点加载好图片之前先加载菊花图,简略来说,先加载菊花图,菊花图加载实现后登程onload回调函数,把img的src设置回本来要加载的图片。

用《JavaScript设计模式与开发实际》中的一句话来总结:代理模式是为一个对象提供一个代用品或占位符,一边管制对它的拜访。

(2)ES6 Proxy

看MDN Proxy会更间接,这里就间接跳过阐明了。

2.代理模式在vue我的项目中的实际

(1)需要剖析

SPA利用一个显著的毛病就是首页加载工夫长,而后其中之一的解决办法就是通过宰割出比拟大的第三方独立库缩小入口文件的体积大小。

在保护公司saas我的项目时,我发现公司根本把echarts,video.js等较大的第三方库都打包在app.js中,按情理能够通过import动静加载把他们都分离出来。但尔后在每个要援用到echarts等的文件中动静引入比拟麻烦。

为了不批改已有的稳固运行的模块的同时实现缩小入口文件的需要,本人通过代理模式实现惰性加载

(2)性能实现

以echarts为例实现惰性加载,先间接贴上代码:

//echarts.js
let handler
let echartFactory
const echart = {
    init: (...args) => {
        let container = {
            // initFlag标记着echart是否已初始化,false代表未初始化,true代表已初始化
            initFlag: false,
            // 记录在未初始化完之前,主函数中要执行的办法都会寄存在fns外面
            fns: [],
            // 待初始化的实例,在加载后所有办法都会在在echart中实现
            echart:undefined
        }
        echartFactory.create(args)
            .then(echart => {
                container.echart = echart
                container.fns.forEach(({ fn, args }) => {
                    if(typeof(echart[fn])==='function')
                        echart[fn].apply(echart, args)
                })
                container.initFlag = true
            })
        return new Proxy(container, handler)
    }
}

handler = {
    get: function (target, key) {
        if (!target.initFlag) {
            // container中的echart未加载时,收集要执行的程序的名称和参数以对象的模式寄存在fns中
            return (...args) => {
                target.fns.push({
                    fn: key,
                    args
                })
            }
        } else {
            // 当echarts初始化实现后,间接返回办法
            if (key in target.echart) {
                return target.echart[key]
            } else {
                return undefined
            }
        }
    }
}

echartFactory = {
    // 寄存从内部加载的echarts模块
    _echart: undefined,
    create: async function (args) {
        if (!this._echart) {
            this._echart = await import(/* webpackPrefetch: true */ 'echarts')
        }
        return this._echart.init.apply(undefined, args)
    }
}

export default echart

从模块导出的echart是一个仅有init办法的对象。当执行init办法时,每次会创立一个名为container的对象,而后通过echartFactory中的create办法初始化container中的echart变量,此过程会在Promise中进行,即在微工作队列中进行。init办法最初返回一个以container为被代理对象、handler为处理器的Proxy实例,此时container中的echart还未被初始化。

echartFactory对象的create办法在执行中会先查看本身的一个公有变量_echart,当其为undefined则开始把echarts第三方库加载进来。当加载实现后通过apply执行init办法且返回后果。

在微工作队列中把返回的后果复制到container对象中的echart,从而实现初始化。实现初始化后,会把fns中的函数和形参通过apply顺次执行。执行完后把initFlag赋值为true,代表整个初始化过程已实现。

之后在app.js入口文件中间接引入即可。

// app.js入口文件

// 援用上述echart.js
import echarts from './echarts.js'
Vue.prototype.$echarts = echarts

(3)实现成果


从bundleAnalyzer生成的宰割图看出echarts确实从app.js中分离出来了。

// 当应用echarts时,不须要扭转原文件的逻辑
...
<script>
export default {
    ...
    methods:{
        initEcharts(){
            this.echarts = this.$echarts.init(document.getElementById('echarts'))
            this.echarts.clear()
            let option={....}
            this.echarts.setOption(option)
        }
    }
    ...
}
</script>

当在例如以上vue文件中应用echarts时,可达到上面的gif成果。

从gif中能够看出切换到应用echarts的路由后才开始加载带有echarts的28.js包。

同样的原理也能够用来实现video.js的惰性加载,这里就间接贴代码不解释了。

import 'video.js/dist/video-js.css'
let handler
let videoFactory

const video = function (...args) {
  let container = {
    initFlag: false,
    fns: [],
    video: undefined
  }
  videoFactory.create(args)
    .then(video => {
      container.video = video
      container.fns.forEach(({ fn, args }) => {
        video[fn].apply(video, args)
      })
      container.initFlag = true
    })
  return new Proxy(container, handler)
}

videoFactory = {
  _video: undefined,
  create: async function (args) {
    if (!this._video) {
      this._video = await import(/* webpackPrefetch: true */ 'video.js')
      this._video = this._video.default
    }
    return this._video.apply(undefined, args)
  }
}

handler = {
  get: function (target, key) {
    if (!target.initFlag) {
      return (...args) => {
        target.fns.push({
          fn: key,
          args
        })
      }
    } else {
      if (key in target.video) {
        return target.video[key]
      } else {
        return undefined
      }
    }
  }
}

export default video

通过下面的拆散,把入口文件从9m多缩小成4.5m左右。

未分离前加载工夫

宰割后的加载工夫:

屡次比照可得出,在生产环境下,宰割后的入口文件加载工夫比宰割前的少1~1.5s。

3.拓展

通过代理模式实现限度申请的并发数量

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理