前言: 项目中的问题首先是没有缓存时进入页面时,点击切换等功能造成的, 这个时候谷歌浏览器中的network中会无缓存请求图片所在的地址,短则1s 长则2s,对用户感觉不是很友好。于是乎第一种解决方法出来了:图片预加载,尽管它仍然有局限性;

图片预加载

let {radarImgArray,radarSource,cloudImgArray,cloudSource} = radarAndCloudObj
        let images = new Array();
        for (let i = radarImgArray.length - 1; i >= 0; i--) {
          images[i] = new Image();
          images[i].src = radarSource + radarImgArray[i];
          images[i].onload = () => {
            if (i == 19) {
              this.drawradarToMap(this.radarIndex);
            }
          };
        }
        for(let i = 0; i < cloudImgArray.length-1;i++){
          images[i] = new Image();
          images[i].src = cloudSource + cloudImgArray[i];
          images[i].onload = () => {
            // console.log(name +'=========>' + i);
            if (i == 19) {
              this.mess.close();
              const h = this.$createElement;
              Message({
                message: h("p", null, [
                  h("span", { style: "color:#42f0ff" }, "数据加载完成 "),
                ]),
                duration: 3000,
                offset: 150,
                type: "success",
                customClass: "messageClass",
              });
            }
          };
        }

好了,上面的代码不用看那么多,讲述一下思路,在你创建一个Image对象并赋予这个image对象的src属性的时候,你可以查看一下network,你会发现他一直在加载。每一张加载完之后都会调用一个onload回调,我们可以在这里面做一些事。

  • 但同时你应该也已经发现了:由于使用的是for循环,一旦轮到这个函数实现的时候,异步队列中顺延下去,我的总共是40张图片需要在一开始加载,这会你在别的地方调用网络请求的时候,你会发现怎么也请求不到,原因很简单,排上了队,但是没轮到它;

交代完前情,我开始考虑第二种去实现这个图片加载的方法,这里想要解决的痛点只有一个,不管他加载与否,只要我想调用别的网络请求就能顺畅响应,一句话来说:我想让我在操作的时候的优先级是最高的

第一时间想到线程相关,假如我可以一个线程去跑这个加载的任务,不会影响到我主线程的任何操作,可是js是单线程的语言,怎么实现多线程的操作呢?一顿搜索:使用web worker

web workers 介绍

在这里插入图片描述
关注上面重点,使用web workers 可以独立于主线程做到运行一个脚本。 注意:worker将运行在于当前window不同的另一个全局上下文中,意味着:你不应该尝试在这里面去操作dom元素,同时Image对象在这里也是找不到的。因为它依赖于window 对象。

照这么解释,使用web workers实现图片预加载这条路就行不通了?

不,可以!但我们也许得试着不从new Image,设置src方向入手。总之我们的核心就是让这个图片在子线程中请求,一般请求我们都是使用ajax,那试试吧。

首先先处理兼容问题,假如用户浏览器版本太低级,你可以使用第一种,反正我是写了两种。
在这里插入图片描述

let worker;
if(Worker !== "undefined"){
	if(typeof worker == "undefined"){
		worker = new Worker("js在本地的路径");
	}
}

上面就完成了如下事情: 独立于主线程运行一个脚本

接着 通过ajax 请求 地址,把它返回的类型设置为blob类型;
在这里插入图片描述
参考代码:

/**
 * 这个js 文件 主要用于 给主线程  跑这 一个 极其需要使用浏览器资源的图片预加载
 * preloadByRadar.js
 */

onmessage = function (radarAndCloudObj) {
    let data = radarAndCloudObj.data;
    let radarUrlArray = []
    for (let i = data.radarImgArray.length - 1; i >= 0; i--) {
        let req = new XMLHttpRequest();
        let image = data.radarSource + data.radarImgArray[i];
        req.open('GET', image, false);
        req.responseType = 'blob';
        req.onreadystatechange = function () {
            if (req.readyState == 4) {
                radarUrlArray.push(req.response);
                self.postMessage(req.response);
                if (i == 19) {
                    self.postMessage('loadRadarToMap');
                }
            }
        }
        req.send(null);
    }
    self.postMessage([radarUrlArray,'radar']);
}

这里ajax我使用了同步请求,没啥,业务需求,异步怕自己把握不住。 将请求的响应类型设置为blob,然后将请求图片得到的二进制流发送至主线程。

onmessage(callback)

用于接收
简单解释下:接收消息,在子线程中onmessage 接收主线程的postMessage 发送过来的消息,反之,主线程worker的onmessage也可以接受到子线程发送的消息。

postmessage()

在这里插入图片描述
第一个参数是对象类型的,可以被发送到线程的。

主线程中的函数调用 项目中完整的调用例子

/**
     * 雷达图 和 云图的 预加载
     * @param {Object} radarAndCloudObj 顾名思义,包含雷达相关属性 跟云图相关属性的对象
     */
    radarAndCloudImgPreload: function (radarAndCloudObj) {
      let radarWorker, cloudWorker;
      let _this = this;
      // 如果用户的浏览器 版本 支持 worker 就使用更好的方式 去执行预加载
      if (typeof Worker !== "undefined") {
        // 这里也许未来 还可以做 优化、  就是在开启一个线程 分别加载
        // 由于我现在使用的方法是同步的ajax 请求 、 其实并不够完美、
        // 应该是: 一个线程加载雷达图  一个线程加载云图 这样子 应该性能会更卓越。
        //后面测过了,实际上差不多。
        // 当然 日后有空  可以进行修改。
        if (typeof radarWorker == "undefined") {
          radarWorker = new Worker("js/preload/preloadByRadar.js");
          cloudWorker = new Worker("js/preload/preloadByCloud.js");
        }
        // 给子线程发送 数据
        radarWorker.postMessage(radarAndCloudObj);
        cloudWorker.postMessage(radarAndCloudObj);
        //  创建 读取二进制流后的url
        var createObjectURL = function (blob) {
          return window[window.webkitURL ? "webkitURL" : "URL"][
            "createObjectURL"
          ](blob);
        };

        // 监听雷达图 子线程发送的消息
        radarWorker.onmessage = function (status) {
          let retData = status.data;
          if (retData == "loadRadarToMap") {
            // 这里我觉得 不应该去做改动
            // 原因如下: 更优秀的编码的价值 不高于修改的价值。
            // 一句话来说 不够优雅~
            _this.drawradarToMap(_this.radarIndex);
          } else if (retData instanceof Array && retData.length == 2) {
            // 处理bloburls  将原本通过拼接请求地址的数组 转换成已经被处理过的请求数组
            _this.blobRadarUrls = retData[0];
            _this.blobRadarUrls.map((item, index, arr) => {
              var newimgdata = createObjectURL(item);
              arr[index] = newimgdata;
            });
            console.log(retData);
          }
        };

        // 监听云图 子线程发送的消息
        cloudWorker.onmessage = function (status) {
          let retData = status.data;
          if (retData == "allPictureInit") {
            // 全部加载完了 ,将原本的提示信息 关闭 , 同时提示数据加载完成
            _this.mess.close();
            const h = _this.$createElement;
            Message({
              message: h("p", null, [
                h("span", { style: "color:#42f0ff" }, "数据加载完成 "),
              ]),
              duration: 3000,
              offset: 150,
              type: "success",
              customClass: "messageClass",
            });
          } else if (retData instanceof Array && retData.length == 2) {
            // 处理bloburls  将原本通过拼接请求地址的数组 转换成已经被处理过的请求数组
            _this.blobCloudUrls = retData[0];
            _this.blobCloudUrls.map((item, index, arr) => {
              var newimgdata = createObjectURL(item);
              arr[index] = newimgdata;
            });
            // 表示数组已经转换完毕 我们下次点击之后 就使用该数组
            _this.blobInit = true;
          }
        };
      }
   }
let radarAndCloudObj = {
        radarImgArray: this.radarImages,
        radarSource: "/api/data/radarpic/",
        cloudImgArray: this.cloudImgArray,
        cloudSource: "/api/data/cloud/IR1/",
      };

讲述下上面代码的核心逻辑:
1、首先主线程运行一个独立于主线程的脚本;
2、接着给这个脚本(子线程)发送数据,我这里后端给我返回的是图片地址的后缀,我得把前面的路径也一起发送过去。
3、把子线程中ajax请求后发送的消息监听到,一个arr,长度为2,当然这里你也可以自己去修改,重点是监听到这个图片数据已经请求完了。
4、将设置在组件中的bloburls 处理,请求的是二进制流,我们需要的是一个可供我们展示的地址
在这里插入图片描述
在这里插入图片描述
5、当全部加载完后,发送一个字符串"allPictureInit"表示加载完成。同时把blobInit设置为true,表示通过我们请求的二进制流转换成的与当前页面相关的domString已经全部可以使用(ps: 你也可以每监听到一张图片被加载,就转换原本的请求地址。看实际需求)。

调用实例

// 将设置 imgsource 的公共逻辑抽离出来
    changeCloudImgSource: function (index) {
      this.cloudImg = this.blobInit
        ? this.blobCloudUrls[index]
        : "http://x.x.x.x/data/cloud/IR1/" + this.cloudImgArray[index];
    },

效果

第一张图片中展示的是:主线程 是 独立于子线程的,无论我在主线程做什么操作,他都会以主线程为主,子线程只有在主线程空闲时才会进行加载。
在这里插入图片描述
第二张图片展示的是: 使用这个经过二进制流转换的url,加载是近乎不需要任何时间的。
在这里插入图片描述

Logo

前往低代码交流专区

更多推荐