前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Thinking--Promise解决动态挂载静态资源重复问题

Thinking--Promise解决动态挂载静态资源重复问题

作者头像
奋飛
发布2025-05-31 16:06:36
发布2025-05-31 16:06:36
6200
代码可运行
举报
文章被收录于专栏:Super 前端Super 前端
运行总次数:0
代码可运行

Thinking系列,旨在利用10分钟的时间传达一种可落地的编程思想。

为减少页面包体积,videojs 相关资源,动态追加到页面。

使用 vue 封装组件 <VideoPlayer>

代码语言:javascript
代码运行次数:0
运行
复制
<template>
    <video ref="videoPlayer" style="width: 100%; height: 100%" class="video-js vjs-big-play-centered" />
</template>
<script>
export default {
  name: 'VideoPlayer',
  data () {
    return {
      player: null
    }
  },
  methods: {
    // 获取资源
    loadResources() {
      if (window.videojs) return Promise.resolve();
      return Promise.all([
        // 见下述,被封装成了Promise
        loadScript('//xxx/js/video-umdV7.18.1-711dd1be5e.js'),
        loadScript('//xxx/js/videojs-ccs-umdV7.18.1-59190bdaf5.css')
      ])
    },
    // 初始化
  	initVideoPlayer() {
      // 需要依赖 videojs 构造函数
      this.player = window.videojs(this.$refs.videoPlayer, {...}, () => {});
    }
  },
  async mounted() {
    await this.loadResources();
    this.initVideoPlayer();
  },
  beforeDestroy() {
    this.player && this.player.dispose();
  },
}
</script>

调用方:

🐬单个组件调用:运转良好

代码语言:javascript
代码运行次数:0
运行
复制
<VideoPlayer v-bind="$attrs"></VideoPlayer>

🐋 循环调用:展示没啥问题,但加载了多份

代码语言:javascript
代码运行次数:0
运行
复制
<div v-for="videoItem in list" :key="videoItem.key">
  <VideoPlayer v-bind="$attrs"></VideoPlayer>
</div>

⚠️ 问题出现了:js 和 css 都被加载了多份(list.length)!

🐾 原因分析:在 loadResources() 中,已经追加了前置判断 if (window.videojs) return Promise.resolve(); 来避免重复资源的加载,从而达到只加载一份的目的。没有生效❓

video.js 资源加载&解析执行完,会在 window 上挂载 videojs 属性。

循环过程会产生多个 <VideoPlayer> 组件实例,进而产生多个获取资源 loadResources() 的调用。加载资源是异步过程window.videojs 资源加载完才会追加。

⚠️注意:在 Chrome 浏览器下,只会发送一个。怀疑 Chrome 做了对应的缓存机制或脚本去重机制,欢迎大家补充&指正~~~

🐬 解决思路:

  1. 将实例创建改成串行,确保第二个初始化时,第一个已经处理完毕(存在),这样 window.videojs 的判断可以生效。=> 可以解决,但不是最佳方案,整体页面渲染效率下降
  2. 初始化多个 <VideoPlayer> 实例(并行加载),确保资源只加载一份。=> ⭐ 资源全局单例
代码语言:javascript
代码运行次数:0
运行
复制
// 借助window实现单例
window.resourceLoader = {
  loadingPromise: null,
  loadResources: function () {
    if (!window.resourceLoader.loadingPromise) {
      window.resourceLoader.loadingPromise = new Promise((resolve, reject) => {
        Promise.all([
          // 见下述,被封装成了Promise
        	loadScript('//xxx/js/video-umdV7.18.1-711dd1be5e.js'),
        	loadScript('//xxx/js/videojs-ccs-umdV7.18.1-59190bdaf5.css')
        ]).then(() => {
          resolve();
        });
      });
    }
    return window.resourceLoader.loadingPromise;
  },
};

export default {
  name: 'VideoPlayer',
  data () {
    return {
      player: null
    }
  },
  methods: {
    // 获取资源(这里使用window)
    loadResources() {
      return window.resourceLoader.loadResources();
    },
    // 初始化
  	initVideoPlayer() {
      // 需要依赖 videojs 构造函数
      this.player = window.videojs(this.$refs.videoPlayer, {...}, () => {});
    }
  },
  async mounted() {
    await this.loadResources();
    this.initVideoPlayer();
  },
  beforeDestroy() {
    this.player && this.player.dispose();
  },
}

🌲 window 单例,确保了全局 window.resourceLoader.loadingPromise 的唯一性! 🌲 loadingPromise:资源加载管理标志,以确保资源只被加载一次。

  • 如果 loadingPromisenull,则意味着资源尚未加载过,加载资源,返回 loadingPromise
  • 否则,返回现有的 loadingPromise,防止冗余加载。

附:body 追加资源

代码语言:javascript
代码运行次数:0
运行
复制
function loadScript(url) {
  // 已加载直接返回
  if (loadScript[url]) return Promise.resolve();
  return new Promise((resolve, reject) => {
    let dom;
    if (url.endsWith('.js')) { // js资源
      dom = document.createElement('script');
      dom.type = 'text/javascript';
      dom.src = url;
    } else { // css资源
      dom = document.createElement('link');
      dom.setAttribute('rel', 'stylesheet');
      dom.setAttribute('type', 'text/css');
      dom.setAttribute('href', url);
    }

    dom.setAttribute('ignore', 'true');
    document.head.appendChild(dom);
    dom.onload = function () {
      loadScript[url] = true;
      resolve();
    };
    dom.onerror = reject;
  });
}

🌴 缓存策略:通过将加载过的脚本URL作为对象的属性记录在loadScript上,实现加载过的资源复用,减少不必要的网络请求。 🌴 动态元素创建:根据URL后缀判断资源类型,如果是.js文件,则创建<script>标签;如果是其他类型(假设为CSS),则创建<link>标签。 🌴 资源加载与事件监听:为新创建的DOM元素添加 onloadonerror 事件处理器,分别在资源成功加载和加载失败时调用 resolvereject ,从而改变 Promise 状态,通知调用者加载结果。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-07-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 附:body 追加资源
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档