完美异步加载javascript并回调函数

稿件来源: 阳光企业网站管理系统   撰稿作者: 太阳光   发表日期: 2015-08-08   阅读次数: 378   查看权限: 游客查看

加载js方法很,但判断是否成功方法却少有。

异步加载js文件,大部分都是生成script标签插入文档,比如我们看看seajs3.0的源码:

    var supportOnload = "onload" in node
    if (supportOnload) {
      node.onload = onload
      node.onerror = function() {
        emit("error", { uri: url, node: node })
        onload(true)
      }
    }
    else {
      node.onreadystatechange = function() {
        if (/loaded|complete/.test(node.readyState)) {
          onload()
        }
      }
    }

从源可以看出只要不支持onload事件的浏览器压根就没有判断onerrer事件,也就是说无法知道文件加载失败情况。

其实主要是IE8及以下浏览器对js无onload事件支持,但支持onreadystatechange事件,而readyState主要有:

0 "uninitialized" 未初始化
1 "loading" 载入中
2 "loaded" 载入完成已经接收到全部响应内容
3 "interactive" 正在解析响应内容
4 "completed" 响应内容解析完成

而且特别是在IE下,往往因先赋值src再插入节点还是先插入节点再赋值src生成readyState返回loaded、completed不一至,所以需要两者一起判断。而且更可气的是不管文件是否成功加载仍会按顺序影响这些readyState,也就是说靠onreadystatechange事件无法分辨是否成功加载js文件了……

突然发现一个点子,把js以vbscript文档加载进入文档,必然会引发window.onerror事件:

    var script = document.createElement("script");
    if ("onload" in script) {
        script.onload = function () {};
        script.onerror = function () {};
    } else {
        script.language = "vbscript";
        var _timeOut = setTimeout(function () {
            alert("失败");
        }, 2000);
        window.onerror = function () {
            clearInterval(_timeOut);
            script = document.createElement("script");
            script.onreadystatechange = function () {
                if (/loaded|complete/i.test(this.readyState)) {alert("成功")}
            };
            script.type = "text/javascript";
            script.src = jsUrl;
            document.getElementsByTagName("head")[0].appendChild(script);
            return false;
        };
    }
    script.src = jsUrl;
    head.appendChild(script);

终于解决关键问题,可是这定时触发失败的时间又是一个问题,到底应该定多长时间为失败呢?经过测试一般js在100KB以内,2秒内完全可以载完。可是的可是假如文件较大或者网速卡了这如何是好?

我们必须等文件载完方可判断是否失败,而且不管文件是否加载成功必须会触发readyState变化,这……经过反复修改终于完美出版此方法:

function loadScript(jsUrl, callBack) {
    /*
     * 加载单个script并回调函数
     * jsUrl : js地址字符串
     * callBack:function(state,node) 回调函数(可选)state = ok|no;node为script节点
     * 原创:348736477@qq.com
     * 作用:加载本地或者网络多个文本文件(除vbscript)大小不限并判断成功与否
     * 如有问题或者改进请通知,谢谢
     * */
    var _doc = document,
            script = _doc.createElement("script"),
            head = _doc.head || _doc.getElementsByTagName("head")[0] || _doc.documentElement;
    'function' != typeof callBack && (callBack = function(){});//确保有回调函数
    if ("onload" in script) {
        //非IE和IE9及以上支持onload、onerror事件
        script.type = "text/javascript";
        script.onload = function () {
            callBack.call(null,"ok",this);
        };
        script.onerror = function () {
            callBack.call(null,"no",this);
        };
    } else {
        var baseElement = head.getElementsByTagName("base")[0],_err = 0;
        script.language = "vbscript";
        //IE8及以下先把js当vbscript文件引入,如果触发window.onerror说明js有效。假如您的脚本也使用了window.onerror请使用监听方法替换以免冲突
        window.onerror = function(){
            _err = 1;
            return true;//忽略错误
        };
        script.attachEvent("onreadystatechange",function(){
            //vbscript成功载入将会先触发window.onerror
            if (/loaded|complete/i.test(script.readyState)) {
                window.onerror = null;//清理监听事件
                if(_err){
                    script.parentNode.removeChild(script);//移除vbscript节点
                    script = document.createElement("script");//再生成script节点并插入文档
                    script.attachEvent("onreadystatechange",function(){
                        if (/loaded|complete/i.test(script.readyState)) {
                            callBack.call(null, "ok", script);
                        }
                    });
                    script.type = "text/javascript";
                    script.src = jsUrl;
                    //IE6下script必须插入到base前,以防引起bug
                    baseElement ? head.insertBefore(script, baseElement) : head.appendChild(script);
                }else{
                    callBack.call(null,"no",script);
                }
            }
        });
    }
    script.src = jsUrl;//必须先赋值后生成,否则影响readyState值
    head.appendChild(script);
}
//调用
loadScript("http://www.scscms.com/1/test.js?a=" + Math.random(),
    function (state,node) {
        alert("ok" == state ? "成功":"失败");
    }
);

最后希望您能测试有问题请通知我,谢谢!

最后的最后我告诉你:它也是有一个小bug呢!假如在IE8以下你加载一个vbscript伪装成的js文件那也会误报哟!

 

=========================假如需要加载多个js文件情况下请使用以下函数===============================

function loadScripts(jsArray,callBack) {
    "use strict";
    /*
     * 按序加载多个script并回调函数
     * jsArray : 单个js地址字符串或者是js文件地址数组
     * callBack:function(state,node) 回调函数(可选)state = ok|no;node为script节点
     * 原创:348736477@qq.com
     * 作用:加载本地或者网络多个文本文件(除vbscript)大小不限并判断成功与否
     * 如有问题或者改进请通知,谢谢
     * */
    var _doc = document,
            script,
            head = _doc.head || _doc.getElementsByTagName("head")[0] || _doc.documentElement,
            baseElement = head.getElementsByTagName("base")[0];
    function loadJs() {
        if(0 == jsArray.length){
            callBack.call(null,"ok",script);
            return;
        }
        var jsUrl = jsArray.shift(),_err = 0;
        script = _doc.createElement("script");
        if ("onload" in script) {
            script.type = "text/javascript";
            script.onload = function () {
                loadJs();
            };
            script.onerror = function () {
                callBack.call(null,"no",this);
            };
        } else {
            script.language = "vbscript";
            //IE8及以下先把js当vbscript文件引入,如果触发window.onerror说明js有效
            //假如您的脚本也使用了window.onerror请使用监听方法替换以免冲突
            window.onerror = function(){
                _err = 1;
                return true;//忽略错误
            };
            script.attachEvent("onreadystatechange",function(){
                //vbscript成功载入将会先触发window.onerror
                if (/loaded|complete/i.test(script.readyState)) {
                    window.onerror = null;//清理监听事件
                    if(_err){
                        script.parentNode.removeChild(script);//移除vbscript节点
                        script = document.createElement("script");//再生成script节点并插入文档
                        script.attachEvent("onreadystatechange",function(){
                            if (/loaded|complete/i.test(script.readyState)) {
                                loadJs();
                            }
                        });
                        script.type = "text/javascript";
                        script.src = jsUrl;
                        //IE6下script必须插入到base前,以防引起bug
                        baseElement ? head.insertBefore(script, baseElement) : head.appendChild(script);
                    }else{
                        callBack.call(null,"no",script);
                    }
                }
            });
        }
        script.src = jsUrl;
        head.appendChild(script);
    }
    '[object Array]' != Object.prototype.toString.call(jsArray) && (jsArray = [jsArray]);//确保参数为数组
    'function' != typeof callBack && (callBack = function(){});//确保有回调函数
    loadJs();
}
//调用
loadScripts(["http://www.scscms.com/1/test.js","http://www.scscms.com/1/jquery-easyui.js","http://www.scscms.com/1/jquerd.js"],
    function (state,node) {
        if("no"==state){
            alert(node.src+"文件加载失败!");
        }else{
            alert("成功!");
        }
    }
);

更新日期:2015-10-12 去除定时器机制

关键词: javascript,异步加载   编辑时间: 2015-10-12 16:45:00

  • 感到高兴

    0

    高兴
  • 感到支持

    0

    支持
  • 感到搞笑

    0

    搞笑
  • 感到不解

    1

    不解
  • 感到谎言

    0

    谎言
  • 感到枪稿

    0

    枪稿
  • 感到震惊

    0

    震惊
  • 感到无奈

    0

    无奈
  • 感到无聊

    0

    无聊
  • 感到反对

    0

    反对
  • 感到愤怒

    0

    愤怒
100%(2)
0%(0)
共有0 条评论 发言请遵守【相关规定

网友评论

会员头像
发 表同步腾讯微博    验证码:  点击更新请先登陆
  • 暂无评论
关闭模块文章图片 article Pictrue
  • 我的妈妈爸爸
  • 基于koa2+mysql+vue2.0+Element阳光内容管理系统
  • 代码覆盖率工具 Istanbul 入门教程
  • 全栈工程师的武器——MEAN
  • 9款超炫的 CSS3 复选框(Checkbox)
  • 微信开发在线翻译功能
  • CSS3那些不为人知的高级属性
  • 给easyui的datebox添加清空事件
  • flash写字效果
  • kendoUI系列教程之DropDownList下拉菜单
  • kendoUI系列教程之datetimepicker日期时间选择
  • kendoUI系列教程之datepicker日期选择
  • kendoUI系列教程之combobox下拉列表框
  • kendoUI系列教程之colorpicker
  • kendoUI系列教程之calendar日历表
  • kendoUI系列教程之autocomplete自动补齐