Skip to content

loadScripts方法

北京前端同事有个项目里用了ace-admin这个管理平台框架,不太喜欢这个框架,不过在里面发现了一段异步加载js脚本文件的代码,修改了一下贴到这里,以后自己项目里也可以用:

js
export function ready(optionalScriptsDependencies, cb) {
  if (arguments.length === 1) {
    waitForReady(cb);
  } else if (arguments.length === 2) {
    loadScripts(optionalScriptsDependencies, () => {
      waitForReady(cb);
    });
  }
}

function waitForReady(cb) {
  $(document).ready(function () {
    cb && cb();
  });
}

// ajaxLoadedScripts要放到loadScripts外才能起到全局统计的作用
const ajaxLoadedScripts = {};
function loadScripts(scripts, callback) {
  $.ajaxPrefilter("script", (opts) => {
    opts.cache = true;
  });
  setTimeout(() => {
    function finishLoading() {
      typeof callback === "function" && callback();
    }

    let deferredCount = 0;
    let resolved = 0;
    for (let i = 0; i < scripts.length; i++) {
      if (scripts[i]) {
        (() => {
          const scriptName =
            "js-" + scripts[i].replace(/[^\w\d-]/g, "-").replace(/--/g, "-");
          ajaxLoadedScripts[scriptName] !== true && deferredCount++;
        })();
      }
    }

    function nextScript(index) {
      index += 1;
      if (index < scripts.length) {
        loadScript(index);
      } else {
        finishLoading();
      }
    }

    function loadScript(index) {
      index = index || 0;
      if (!scripts[index]) {
        return nextScript(index);
      }

      const scriptName =
        "js-" + scripts[index].replace(/[^\w\d-]/g, "-").replace(/--/g, "-");
      // 只加载目前为止没有加载过的脚本
      if (ajaxLoadedScripts[scriptName] !== true) {
        $.getScript(scripts[index])
          .done(() => {
            ajaxLoadedScripts[scriptName] = true;
          })
          .complete(() => {
            ++resolved >= deferredCount ? finishLoading() : nextScript(index);
          });
      } else {
        nextScript(index);
      }
    }

    deferredCount > 0 ? loadScript() : finishLoading();
  }, 10);
}

使用方法很简单,碰到有js文件依赖的时候,这样使用ready方法:

js
ready(["../assets/js/jquery.validate.js"], () => {
  // 这时依赖的js脚本文件已经加载完毕了,document ready后要执行的代码放在此处
});

如果没有js文件依赖的话,这样使用ready方法:

js
ready(() => {
  // document ready后要执行的代码放在此处
});

下面是根据以前自己写过的方法封装一下得到的代码,作用差不多(没像上面的例子那样判断脚本是否已加载过,不过用document.write生成script标签加载js文件的话,浏览器自己也会做缓存处理的,问题不大吧-\_-):

js
function loadAsyncScripts(scripts, callback) {
  const scriptsLength = scripts.length;
  scripts.forEach((item) =>
    loadAsyncScript(
      item,
      checkWhetherToExecuteCallback.bind(null, scriptsLength, callback),
    ),
  );
}

let counter = 0;
function checkWhetherToExecuteCallback(scriptsLength, cb) {
  ++counter === scriptsLength && cb();
}

function loadAsyncScript(src, callback) {
  var head = document.getElementsByTagName("head")[0];
  var script = document.createElement("script");
  script.setAttribute("type", "text/javascript");
  script.setAttribute("src", src);
  script.setAttribute("async", true);
  script.setAttribute("defer", true);
  head.appendChild(script);

  if (document.all) {
    script.onreadystatechange = function () {
      var state = this.readyState;
      if (state === "loaded" || state === "complete") {
        callback();
      }
    };
  } else {
    script.onload = function () {
      callback();
    };
  }
}

天上的神明与星辰,人间的艺术和真纯,我们所敬畏和热爱的,莫过于此。