跨浏览器的设置innerHTML方法

定义
  • 代码:这里说的代码是指要插入到 innerHTML 中的 html 代码,不是 JavaScript 代码
  • 脚本:代码中包含的 JavaScript 脚本,也就是 html 代码中的 <script> 元素
  • 本地脚本:直接写在 html 代码中的 JavaScript 脚本元素,例如:
    <script language="javascript">alert("Hello World");<script>
  • 外部脚本:连接到其它 JavaScript 文件的脚本元素
    <script language="javascript" src="helloworld.js"><script>
  • 样式:代码中包含的 CSS 样式,也就是 html 代码中的 <style> 元素
问题

很多人都可能遇到过这种情况:使用 innerHTML 来插入 html 代码,如果代码中包含脚本或者样式,这些脚本和样式就会不生效,或者在 IE 上生效在其它浏览器上不生效。原因很简单:不同浏览器对插入 innerHTML 中的脚本和样式有不同的处理方法。

分析
  • 对于 IE,当用 innerHTML 的方式加载的时候,它会立刻执行所有本地脚本,而外部脚本会在后台分别加载。还有,如果 style 或者 script 元素之前没有可显示元素,IE 会滤掉这些元素。
  • 对于 Firefox,所有脚本都放在后台执行,html 代码加载过程不会等待脚本的加载,立刻返回。
  • 对于 Opera,所有脚本按顺序,并且在 html 代码加载时执行。
  • 其次,如果加载的脚本中包含 document.write,会破坏原页面。

针对上面分析,这里给出两个版本的解决方法,它们各有优缺点。

复杂版

复杂版会严格控制代码中各个脚本的加载顺序,并且处理了脚本中包含 document.write 的问题。代码如下:

 

/* ------------------------------------------------------------------
 
    Cross-browser set innerHTML method.
    The HTML code to be inserted can contains script and style tag.
 
    Tested browsers:
        IE 5.5+
        Firefox 1.0+
        Opera 8.5+
 
    Author: kenxu <ken@ajaxwing.com>
    Version: 0.1.5
    Date: 2006-08-04
 
    Usage:
        setInnerHTML(element, htmlCode);
 
    For more informations, visit:
        http://www.ajaxwing.com/index.php?id=3
 
   ------------------------------------------------------------------ */
 
var setInnerHTML = (function () {
var element_stack = [];
var input_stack = [];
var html_stack = [];
var timer = null;
var ua = navigator.userAgent.toLowerCase();
var isIE = (ua.indexOf('msie') >= 0 && ua.indexOf('opera') < 0);
var old_document_write = document.write;
var old_document_writeln = document.writeln;
var loding_script = false;
 
var callback = function () {
    if (loding_script) {
        return;
    }
    if (element_stack.length == 0) {
        clearInterval(timer);
        timer = null;
        document.write = old_document_write;
        document.writeln = old_document_writeln;
        return;
    }
    var index = element_stack.length - 1;
    var input = input_stack[index];
    if (input.length == 0) {
        input_stack.pop();
        var element = element_stack.pop();
        var html = html_stack.pop();
        element.innerHTML = '';
        if (typeof beforeInsert == 'function') {
            html = beforeInsert(html);
        }
        if (html.match(/<script([^>]*>)((.|r|n)*?)</script>/i) != null) {
            setInnerHTML(element, html);
            return;
        }
        if (isIE) {
            html = '<div style="display:none">for IE</div>' + html;
            element.innerHTML = html;
            element.removeChild(element.firstChild);
        } else {
            element.innerHTML = html;
        }
        return;
    }
    var item = input[input.length - 1];
    if (typeof item == 'string') {
        html_stack[index] += item;
        input.pop();
    } else if (typeof item == 'object') {
        if (item.src) {
            loding_script = true;
            var script = document.createElement('script');
            script.src = item.src;
            script.__index = index;
            if (isIE) {
                script.onreadystatechange = script_loaded;
            } else {
                script.onload = script_loaded;
            }
            var head = document.getElementsByTagName('head')[0];
            head.appendChild(script);
        }
        if (item.text) {
            var script = document.createElement('script');
            script.text = item.text;
            var head = document.getElementsByTagName('head')[0];
            head.appendChild(script);
            input.pop();
        }
    } else {
        input.pop();
    }
}
 
var script_loaded = function () {
    if (isIE && this.readyState.toLowerCase() != "loaded" && this.readyState.toLowerCase() != "complete") {
        return;
    }
    var index = this.__index;
    input_stack[index].pop();
    loding_script = false;
}
 
var new_document_write = function() {
    for (var i = 0; i < arguments.length; i++) {
        html_stack[element_stack.length - 1] += arguments[i];
    }
}
 
var new_document_writeln = function () {
    for (var i = 0; i < arguments.length; i++) {
        new_document_write(arguments[i] + "n");
    }
}
 
return function (element, htmlCode) {
    element_stack.push(element);
    html_stack.push('');
    var input = [];
    while (true) {
        if ((m = htmlCode.match(/<script([^>]*>)((.|r|n)*?)</script>/i)) == null) {
            break;
        }
        input.unshift(htmlCode.substr(0, m.index));
        htmlCode = htmlCode.substr(m.index + m[0].length);
        if ((m2 = m[1].match(/srcs*=s*(['"]?)([^'">s]*)1/i)) != null) {
            input.unshift({src:m2[2]});
        } else {
            input.unshift({text:m[2]});
        }
    }
    input.unshift(htmlCode);
    input_stack.push(input);
    if (timer == null) {
        document.write = new_document_write;
        document.writeln = new_document_writeln;
        timer = setInterval(callback, 10);
    }
}})();

如果你想在最终插入之前修改一下代码,可以定义一个名为 beforeInsert 的函数,例如:

function beforeInsert (html) {
    // 在这里修改 html
    return html;
}

setInnerHTML 会先执行 beforeInsert,然后才插入代码。在你并不知道最终要插入的代码是什么的情况下,beforeInsert 函数是非常有用的。

复杂版的缺点有两个:第一,代码中的脚本不能访问到代码中的其它 html 元素;第二,复杂版的执行速度和兼容性不如简单版。

简单版

如果脚本中不包含 document.write,则不用保证脚本加载顺序的问题,因为各个浏览器有其自身的加载逻辑,只需确保脚本和样式都生效就可以了。代码如下:

/*
 * 描述:跨浏览器的设置 innerHTML 方法
 *       允许插入的 HTML 代码中包含 script 和 style
 * 作者:kenxu <ken@ajaxwing.com>
 * 日期:2006-03-23
 * 参数:
 *    el: 合法的 DOM 树中的节点
 *    htmlCode: 合法的 HTML 代码
 * 经测试的浏览器:ie5+, firefox1.5+, opera8.5+
 */
var setInnerHTML = function (el, htmlCode) {
    var ua = navigator.userAgent.toLowerCase();
    if (ua.indexOf('msie') >= 0 && ua.indexOf('opera') < 0) {
        htmlCode = '<div style="display:none">for IE</div>' + htmlCode;
        htmlCode = htmlCode.replace(/<script([^>]*)>/gi,
                                    '<script$1 defer>');
        el.innerHTML = htmlCode;
        el.removeChild(el.firstChild);
    } else {
        var el_next = el.nextSibling;
        var el_parent = el.parentNode;
        el_parent.removeChild(el);
        el.innerHTML = htmlCode;
        if (el_next) {
            el_parent.insertBefore(el, el_next)
        } else {
            el_parent.appendChild(el);
        }
    }
}

简单版充分利用了浏览器自身的特性,执行效率高,兼容性好。唯一的缺点就是脚本中不能包含 document.write。

应用场合

复杂版非常适合用来插入广告代码。广告代码一般使用 document.write 来直接在页面中插入 html,而 document.write 是阻塞的,也就是说浏览器会等待广告代码执行完毕才显示下面的内容,如果广告的执行速度比较慢或者你的页面中放置了多个广告,那么整个页面的显示时间就会变得很慢,但实际上浏览器很早就已经拿到整个页面了。使用 setInnerHTML 就可以加快整个页面的显示速度,因为浏览器不必等待每一个广告代码执行完毕。

别看简单版只有 20 行代码,但她是非常强大的,保证代码按照各个浏览器自身的逻辑加载。也就是说你也可以插入任意 html 代码,只要脚本中不包含 document.write。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值