专栏首页前端学习归纳总结异步加载脚本保持执行顺序

异步加载脚本保持执行顺序

首先是外部脚本和行内脚本,对于异步加载的脚本,会导致竞争状态,使得出现未定义的错。

采用Script Dom技术测试:

代码:

<script type="text/javascript"> 

  var scriptElem = document.createElement('script');
  scriptElem.src = "js/jquery-2.1.1.js";

  document.getElementsByTagName('head')[0].appendChild(scriptElem);

</script>

<script type="text/javascript">

     function test(){

        $("#test").addClass('class_name'); }

     test();

</script>

运行结果:

以下几种方式解决该问题:

1.硬编码回调

将test方法的执行定义在外部脚本(即调用的脚本),该方法不灵活,如果调用的是第三方脚本的话,更加麻烦。此处不显示例子。

2.Window onload: 通过监听window的onload事件来触发行内代码的执行。只要确保外部脚本在window。Onload之前下载执行就可以保持执行顺序。

运行结果:

代码:

function test(){

  $("#test").addClass('class_name');

  }

  if(window.addEventListener){

     window.addEventListener("load", test, false);

  }else if(window.attachEvent){

     window.attachEvent("onload",test);

  }

 

缺点:1.必须确保异步脚本是通过阻塞onload事件的方式加载的。

         2.如果页面有更多的资源,那么外部脚本可能在onload时间出发之前早就完成加载,一般来说,行内脚本最好在外部脚本下载和执行完成之后立即调用。

3.定时器:

采用轮询方法来抱着在行内脚本执行之前所依赖的外部脚本已经加载。

运行结果:

代码:

function test(){

  $("#test").addClass('class_name');

  console.log(index);

  }

var index = 0;

  function initTimer(){

    if("undefined" === typeof($)){

      index++;

      setTimeout(initTimer,300)

    }else{

      test();

    }

  }

  initTimer();

缺点:如果在setTimeout方法中设置的时间太小,会造成额外的开销。设置太大会导致和windon.onload的方法一样,脚本加载完成无法立即执行行内脚本。另外,如果脚本出错,轮询会无限进行下去。

4.Script onload:

前面提到的整合技术会增加页面的脆弱性、延迟和开销,通过监听脚本的onload事件可以解决这些问题。

运行结果:

代码:

function test(){

  $("#test").addClass('class_name');

  }

var scriptElem = document.createElement('script');

  scriptElem.src = "js/jquery-2.1.1.js";

  scriptElem.onloadDone = false;

  scriptElem.onload = function(){

    scriptElem.onloadDone = true;

    test();

  }

  scriptElem.onreadyStatechange  = function(){

    if(("loaded" === scriptElem.readyState || "complete" === scriptElem.readyState) && !scriptElem.onloadDone){

       scriptElem.onloadDone = true;

    test();

    }

  }

  document.getElementsByTagName('head')[0].appendChild(scriptElem);

优点:维护简单,事件处理也简单,整合异步加载外部脚本和行内脚本的首选。

5.降级使用script标签:

即用一个标签即包含外部脚本,又使用行内脚本,如下:

<script src=" js/jquery-2.1.1.js "  >

  function test(){

  $("#test").addClass('class_name');

  }

</script>

由于浏览器并不支持这种模式,所以需要在脚本的内部增加代码来执行行内脚本,找到该脚本,并用eval执行其内容。如下:

var scripts = document.getElementsByTagName('script');

         var cntr = scripts.length;

         while(cntr--){

      var curScript = scripts[cntr-1];

      if(-1 != curScript.src.indexOf("js/jquery-2.1.1.js")){

             eval(curScript.innerHTML);

             break;

      }

      cntr--;

         }

此处不给出例图,具体代码如下:

function test(){

    $("#test").addClass('class_name');

  }

  var scriptElem = document.createElement('script');

  scriptElem.src = "js/jquery-2.1.1.js";

  if(-1 != navigator.userAgent.indexOf("Opera")){

       scriptElem.innerHTML = "test()";

  }else{

       scriptElem.text = "test()";

  }

document.getElementsByTagName('head')[0].appendChild(scriptElem);

优点:技术优雅简洁,开销最小。

缺点:需要修改外部脚本,对第三方库不适用。

多个脚本按序执行:

正常引入脚本:

运行结果:

采用XHR eval:

运行结果:

由于脚本没有按顺序执行,出现未定义的错误。

解决方法1:Managed XHR

通过EFWS.Script模块封装了一种技术,将XHR响应加入队列来保证它们按顺序执行。

代码:

/*
  数组queuedScripts存储执行队列中的脚本,每个脚本是拥有三个属性的对象:
  response: XHR响应
  onload: 脚本加载后触发的函数
  bOrder: 如果该脚本需要依赖其他脚本按顺序执行,则设为true
*/
  EFWS.Script = {
    queuedScripts: [],

    //传入三个参数,第二个参数可选
    loadScriptXhrInjection: function(url, onload, bOrder) {
        var iQ = EFWS.Script.queuedScripts.length;

    //如果需要按顺序执行,并将脚本对象放入数组
        if (bOrder) {
            var qScript = {response: null, onload: onload, done: false};
            EFWS.Script.queuedScripts[iQ] = qScript;
        }

    //调用AJAX
        var xhrObj = EFWS.Script.getXHRObject();
        xhrObj.onreadystatechange = function() {
            if (xhrObj.readyState == 4) {

    //如果第三个参数的值为true,即调用injectScripts()函数
                if (bOrder) {
                    EFWS.Script.queuedScripts[iQ].response = xhrObj.responseText;
                    EFWS.Script.injectScripts();

    //如果不需要按顺序执行,即立即加载脚本
                } else {
                    eval(xhrObj.responseText);
                    if (onload) {
                        onload();
                    }
                }
            }
        };
        xhrObj.open('GET', url, true);
        xhrObj.send('');
    },

    //遍历数组,当发现某一脚本加载但未执行时,立即执行
    injectScripts: function() {
        var len = EFWS.Script.queuedScripts.length;
        for (var i = 0; i < len; i++) {
            var qScript = EFWS.Script.queuedScripts[i];

    //已加载的脚本
            if (!qScript.done) {

    //如果响应未返回 立即停止
                if (!qScript.response) {
                    break;

    //执行脚本
                } else {
                    eval(qScript.response);
                    if (qScript.onload) {
                        qScript.onload();
                    }
                    qScript.done = true;
                }
            }
        }
    },

    //AJAX对象
    getXHRObject: function() {
        var xhrObj = false;
        try {
            xhrObj = new XMLHttpRequest();
        }
        catch(e) {
            var aTypes = ["Msxm12.XMLHTTP6.0",
                          "Msxm12.XMLHTTP3.0",
                          "Msxm12.XMLHTTP",
                          "Microsoft.XMLHTTP"];
            var len = aTypes.length;
            for (var i = 0; i < len; i++) {
                try {
                    xhrObj = new ActiveXObject(aTypes[i]);
                }
                catch(e) {
                    continue;
                }
                break;
            }
        }
        finally {
            return xhrObj;
        }
    }
};


//调用脚本
EFWS.Script.loadScriptXhrInjection("js/jquery-2.1.1.js", null, true);
EFWS.Script.loadScriptXhrInjection("js/first.js", null, true);
EFWS.Script.loadScriptXhrInjection("js/second.js", null, true);

 运行结果:

缺点:必须同域。

当脚本不同域时,可以采用Script Dom Element 和document.write Script Tag的方法。

由于document.write Script Tag在并行下载脚本时会阻塞其他资源,而Script Dom Element则只在FireFox(实际测试FireFox并不行,可能是版本原因)和Opeare按序执行,所以应在不同浏览器采用不同方法。

代码:

var ScriptLoader ={};
   ScriptLoader.script = {
       loadScriptDomElement:function(url, onload){
            var script = document.createElement ("script")
            script.type = "text/javascript";
            if (script.readyState){ //IE
                script.onreadystatechange = function(){
                    if (script.readyState == "loaded" || script.readyState == "complete"){
                        script.onreadystatechange = null;
                        if(onload)
                        onload();
                    }
                };
            } else { //Others
                script.onload = function(){
                if(onload)
                    onload();
                };
            
            }
            script.src = url;
            document.getElementsByTagName("head")[0].appendChild(script);
        },    

        loadScriptDomWrite: function(url,onload){
            document.write('<script  src="'+url+'" type="text/javascript"></scr'+'ipt>');  
            if(onload){
                if(elem.addEventListener){//others
                    elem.addEventListener(window,'load',onload);
                }else if(elem.attachEvent){ //IE
                    elem.addEventListener(window,'onload',onload);
                }
            }
        },

        //根据浏览器选择浏览器加载js的方式
        loadScript: function(url,onload){
                if(
                   -1 != navigator.userAgent.indexOf('Opera')){
                    //当浏览器为firefox和opera时通过Script Dom Element 保证脚本执行顺序
                        ScriptLoader.script.loadScriptDomElement(url,onload); 
                }else{
                    //当为其他浏览器时,通过document write Script保证脚本执行顺序。此时脚本的加载会阻塞其他资源,这是一种折衷
                        ScriptLoader.script.loadScriptDomWrite(url,onload);
                }
        }  
}

//调用脚本
ScriptLoader.script.loadScript("js/jquery-2.1.1.js", null);
ScriptLoader.script.loadScript("js/first.js", null);
ScriptLoader.script.loadScript("js/second.js", null);

运行结果(write):

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CSS盒模型及边距问题

    盒模型是CSS的基石之一,页面的每一个元素都被看作一个矩形框,分别由外边距,边框,内边距,内容组成,

    菜的黑人牙膏
  • VUE 组件通信总结

    https://cn.vuejs.org/v2/guide/state-management.html

    菜的黑人牙膏
  • CSS定位概述

    如果对一个元素进行相对定位,它将出现在它所在的位置上,然后可以通过设置垂直或者水平位置,让这个元素“相对于”它原来的位置进行移动,这时元素依然占据原来的位置,但...

    菜的黑人牙膏
  • 推荐优秀弹出层组件:layer

    以前用artDialog较多,包括DTcms中用得也是artDialog弹出框,并做了jQuery封装。去年开始了解到Layer,就喜欢上了,并在多个项目中使用...

    崔文远TroyCui
  • Python向上取整,向下取整以及四舍五入函数

    hankleo
  • Web前端学习 第4章 jQuery 1 jQuery概述

    jQuery是JavaScript的一个库,jQuery 极大地简化了 JavaScript 编程。我们在做网站或web应用的过程中,需要用JavaScript...

    学习猿地
  • 在GitHub上发布一个Python项目需要注意哪些

    本篇介绍个人或企业在 GitHub 上发布一个 Python 项目需要了解和注意哪些内容

    Peter Shen
  • 首台深度学习超级计算机交付 人工智能大战正酣

    用户1908973
  • 达观数据如何打造一个中文NER系统

    1 NER简介 NER(Named Entity Recognition,命名实体识别)又称专名识别,是自然语言处理中常见的一项任务,使用的范围非常广。命名实体...

    达观数据
  • 选择排序、归并排序、快速排序。

    选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。

    大猫的Java笔记

扫码关注云+社区

领取腾讯云代金券