移除script标签引起的兼容性问题

一、应用场景:

有时候我们需要动态创建script标签实现脚本的按需加载,我们会为script标签绑定onload或者onreadystatechange事件,用于检测动态脚本是否加载并执行完毕,在事件处理程序中引用动态脚本创建的变量。

二、问题描述:

如果我们动态创建script标签并绑定事件处理程序后,立即移除script标签,那么事件处理程序是否会执行?如果能执行,那么能否正常引用动态脚本创建的变量?

三、看如下程序:

function inviteFriends() {
    var self = this;

    if ($("#onlineserviceMailToFriend").size() > 0) {
        showMail();
    } else {
        var jsUrl = "http://images.139cm.com/mpost2014/js/mpost/onlineservice/m2012.mpost.onlineservice.view.mailtofriend.js?v=1.0";
        
        // 动态创建script标签加载脚本
        M139.core.utilCreateScriptTag({
            id : "onlineserviceMailToFriend",
            src : jsUrl,
            charset : "utf-8"
        }, showMail);
    }

    // 异步请求脚本之后马上移除script标签
    $("#onlineserviceMailToFriend").remove();

    // script标签的onload或onreadystatechange事件处理程序
    function showMail() {
        if (!self.mailToFriend) {
            // IE系列会报错找不到对象:M2012.Mpost.OnlineService.View.MailToFirend
            // chrome浏览器可正常运行
            self.mailToFriend = new M2012.Mpost.OnlineService.View.MailToFirend({
                model : self.model
            });
        }
        self.mailToFriend.render();
    }
}
/**
 *动态加载script标签
 *@param {Object} options 配置
 *@param {Stirng} options.id script标签的id ;
 *@param {Stirng} options.src JS文件地址(完整路径);
 *@param {Stirng} options.charset 给script标签加charset属性
 *@param {Function} callback 加载完成的回调
 *@example
 *M139.core.utilCreateScriptTag(
 {
 id:"examplejs",
 src:"http://images.139cm.com/m2012/richmail/js/example.js",
 charset:"utf-8"
 },
 function(){
 alert("文件加载完毕");
 }
 *);
 */
function utilCreateScriptTag(options, callback) {
    var This = this;
    if (callback) {
        var _callback = callback;
        var callback = function() {
            _callback.call(This);
        }
    }
    var scriptId = options.id;
    var dataHref = this.getScriptPath(options.src);
    var charset = options.charset;
    var isReady = false;
    var head = document.getElementsByTagName("head")[0];
    var objScript = scriptId && document.getElementById(scriptId);
    //是否移出脚本DOM(非IE9时处理)
    var isRemoveScriptDom = !document.all && true || false, browserVersion = ["msie 10.0", "msie 9.0", "chrome", "firefox"], i = 0, bvLenght = browserVersion.length - 1, currVersion = window.navigator.userAgent.toLowerCase() || "";
    //IE9、chrome、firefox时处理
    while (i <= bvLenght) {
        isRemoveScriptDom = currVersion.indexOf(browserVersion[i]) > -1 && true || false;
        if (isRemoveScriptDom) {
            break;
        }
        i++;
    }
    browserVersion = null;
    try {
        if (objScript && isRemoveScriptDom) {
            objScript.src = "";
            objScript.parentNode.removeChild(objScript);
            objScript = null;
        }
    } catch (e) {
    }
    if (objScript != null) {
        if (dataHref.indexOf("?") == -1)
            dataHref += "?";
        dataHref += "&" + Math.random();
        objScript.src = dataHref;
        var dataScript = objScript;
    } else {
        var dataScript = document.createElement("script");
        if (scriptId) {
            dataScript.id = scriptId;
        }
        if (charset) {
            dataScript.charset = charset;
        }
        try {
            if (dataHref.indexOf("?") == -1) {
                dataHref = M139.Text.Url.makeUrl(dataHref, {
                    sid : top.$App.getSid()
                });
            }
        } catch (e) {
        }
        dataScript.src = dataHref;
        dataScript.defer = true;
        dataScript.type = "text/javascript";
        head.appendChild(dataScript);
    }
    if (document.all) {
        dataScript.onreadystatechange = function() {
            if (dataScript.readyState == "loaded" || dataScript.readyState == "complete") {
                isReady = true;
                if (callback)
                    callback();
            }
        }
    } else {
        dataScript.onload = function() {
            isReady = true;
            if (callback)
                callback();
        }
        dataScript.onerror = function() {
            isReady = true;
            if (callback)
                callback();
        }
    }
}

四、结论:

由于函数utilCreateScriptTag是通过属性赋值的方式注入事件处理程序,而移除脚本调用的是jQuery的remove方法,dom虽然移除但事件依然存在于内存,所以当浏览器加载脚本并执行完毕,事件处理程序依然会被调用,这一点IE浏览器与标准浏览器表现一致,但是当试图在事件处理程序中引用动态脚本创建的变量时,IE系列无一例外的报错,而标准浏览器却可以正常引用。

原文地址:https://www.cnblogs.com/hellohuman/p/3946311.html