vue项目加载第3方js优化

8

项目写到最后需要优化的时候,发现有很多首屏用不到的第3方js都写在index.html里,严重拖慢的网页的加载速度,这里的第3方组件大多不能通过npm或其他模块安装,所以不能直接用vue里的异步组件,比如高德地图的jssdk和它的ui组件,最简单的方法就是在document里插入script。 所以就有了下面的方案。

先定义一个把js插入到document里的方法

# asyncLoadJs.js
export default function asyncLoadJs (url) {
  return Q.Promise((resolve, reject) => {
    let hasLoaded = $('script[src="'+url+'"]').length > 0
    if (hasLoaded) {
      resolve()
      return
    }

    let script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = url
    document.body.appendChild(script)
    script.onload = () => {
      resolve()
    }
    script.onerror = () => {
      reject()
    }
  })
}

在按第3方js依赖的不同 分成各个函数

这里以我项目里的高德地图组件为例,它需要一个主jssdk库和一个提供ui的库,ui库是依赖主库的。

# asyncLoadJs.js
export function loadAMapJS () {
  return Q.Promise((resolve, reject) => {
    asyncLoadJs('https://webapi.amap.com/maps?v=1.3&key=[你自己的key]&plugin=AMap.ToolBar,AMap.Geolocation,AMap.Autocomplete')
      .then(() => {
        return asyncLoadJs('https://webapi.amap.com/ui/1.0/main.js')
      })
      .then(() => {
        resolve()
      })
      .catch(err => {
        reject(err)
      })
  })
}

在组件生命周期中使用异步加载

# position-picker.vue
<script>
    import {loadAMapJS} from '../../libs/asyncLoadJs'
    
    let loadedAMapJS = false // 是否加载完js
    
    export default {
        created () {
          // 判断是否加载过
          if (!loadedAMapJS) {
            loadAMapJS().then(() => {
              loadedAMapJS = true
            })
          }
        },
        mounted () {
          // 循环判断有没有加载完 写在mounted生命周期里是应为高德的api依赖dom
          let interval = setInterval(() => {
            if (loadedAMapJS) {
              clearInterval(interval)
              this.init()
            }
          }, 300)
        },
        init () {
            let AMap = window.AMap
            let AMapUI = window.AMapUI
            AMapUI.loadUI(['misc/PositionPicker'], function (PositionPicker) {
              self.map = new AMap.Map(self.$refs['map'], {
                zoom: 16,
                scrollWheel: false
              })
              ...其他代码
            })
        }
    }
</script>

一个页面有多个第3方组件的问题

如果一个页面有多个第3方组件,那只定义一个是否加载完的标志位是不够的,因为组件基本是同时created的,在dom中插入script标签后都会返回resolve,其实这时js是没有加载完的,这时可以加一个是否是第一次请求js的变量。

# position-picker.vue
<script>
    import {loadAMapJS} from '../../libs/asyncLoadJs'
    
    let loadedAMapJS = false // 是否加载完js
    let firstLoadingAMapJS = true // 否是第一次请求
    
    export default {
        created () {
          // 判断是否加载过
          if (!loadedAMapJS && firstLoadingAMapJS) {
            firstLoadingAMapJS = false // 马上置为false 只让第一个created组件去请求js
            loadAMapJS().then(() => {
              loadedAMapJS = true
            }).catch(() => {
              firstLoadingAMapJS = true // 出错置为true 下次进来还是能重新请求js
            })
          }
        }
        ...
    }
</script>

注意事项

在computed或watch中,如果需要用到第3方js方法的地方必须先判断第3方js是否加载完,不然会报错的~

你可能感兴趣的

Billy · 2017年07月08日

博主这种加载高德API的方式有没遇到过“文档流已关闭,无法插入script的错误”?(我之前尝试的时候调试关键没问题,build为production后就出现这个错误了,似乎是高德API自己又会新插入script标签造成的,没有去深究)似乎高德提供的异步加载完成后调用回调函数的方式比较安全,可以在回调里面再去加载UI库,至于动态加载script,npm上有一些类似load-script之类的库可以实现,但接口不是promise的,需要自己包装一下。最后为什么不考虑在mounted里面去加载,直接在promise.then里面执行init呀?created和mounted执行时机相差不大吧,用setInterval感觉有损逼格呀,原谅我是处女座?

+1 回复

0

果然正式环境会报错,应该是高德js里写了 document.write 应该用append方法往dom里添加。

yang_j_j 作者 · 2017年07月08日
michaelzhouh · 2017年09月06日

请问,如果不是cdn 方式呢?
我现在的代码是全在main.js
import AMap from 'vue-amap';
AMap.initAMapApiLoader({……});
Vue.use(AMap);

LZ能不能改成脚手架的方式呢?以及配合webpack单独打包amap的js,3Q

回复

载入中...