讓插入到 innerHTML 中的 script 跑起來(lái)的實(shí)現(xiàn)代碼
這個(gè)問(wèn)題在某些時(shí)候微不足道,甚至可以忽略,但有些時(shí)候,這個(gè)問(wèn)題就非常嚴(yán)重,它很可能讓我們的程序得不到預(yù)期的結(jié)果。因此我們需要解決這個(gè)問(wèn)題。
如果你讀過(guò) MSDN,你會(huì)發(fā)現(xiàn)并非所有插入到 innerHTML 中的腳本都不能執(zhí)行,如果這段腳本的 script 標(biāo)簽中包含了 defer 屬性,IE 會(huì)正確的執(zhí)行這些腳本程序。但不幸的是,Moziila/Firefox 和 Opera 可不吃這一套,不管 script 標(biāo)簽有沒(méi)有設(shè)置 defer 屬性,這些瀏覽器都不會(huì)向 IE 那樣去執(zhí)行插入到 innerHTML 中的腳本。
但不管腳本是否被執(zhí)行了,有一點(diǎn)我們可以肯定,那就是這些腳本確實(shí)被插入到了 innerHTML 中,如果不相信,你可以 alert 一下看看。但如果你真的 alert 了,你也可能會(huì)發(fā)現(xiàn)有一種例外情況存在,那就是如果腳本在 innerHTML 內(nèi)容開(kāi)頭的話,那么 IE 瀏覽器將會(huì)忽略掉這段腳本,而 Moziila/Firefox 和 Opera 卻不會(huì)。
好了,問(wèn)題分析的差不多了,我們來(lái)看看如何解決吧。
解決的思路其實(shí)很簡(jiǎn)單,那就是將插入到 innerHTML 中的所有腳本取出來(lái),然后一一執(zhí)行。不過(guò)我們先要解決上面的兩個(gè)問(wèn)題。
先來(lái)看第一個(gè)問(wèn)題,如何避免在 IE 中重復(fù)執(zhí)行 innerHTML 中帶有 defer 屬性的腳本。這個(gè)很容易,只需要先確定瀏覽器是否是 IE,然后再檢測(cè)將要執(zhí)行的腳本是否帶有 defer 屬性即可。需要注意的是,在判斷 IE 瀏覽器時(shí),我們需要避免被 opera 的瀏覽器識(shí)別欺騙。這一點(diǎn)我們?cè)诤竺娴拇a中將會(huì)看到它是如何做的。
接下來(lái),看 IE 忽略 innerHTML 開(kāi)頭腳本的問(wèn)題,這個(gè)也很容易解決。只需要在要插入到 innerHTML 中的內(nèi)容的開(kāi)頭附加一段不是腳本的內(nèi)容,就可以了。但不要試圖附加一個(gè)空內(nèi)容的標(biāo)簽,或者空格、回車、換行等,這將不起作用,開(kāi)頭的腳本仍然會(huì)被忽略。也不要試圖附加 ,雖然這可以讓開(kāi)頭的腳本不再被忽略,但這個(gè) 仍然會(huì)影響原有內(nèi)容的顯示,雖然你可能覺(jué)得不明顯,但是對(duì)于挑剔的用戶來(lái)說(shuō),這可能是無(wú)法容忍的。因此,為了讓附加的內(nèi)容既可以起到避免開(kāi)頭腳本被忽略的功能,又不會(huì)造成不良影響,我們將附加這么一段內(nèi)容:
<span style="display: none">hack ie</span>
雖然上面這段內(nèi)容有一定的長(zhǎng)度,但是它并不會(huì)顯示,而且這個(gè)插入的標(biāo)簽沒(méi)有 id 也沒(méi)有 name,所以也不會(huì)跟原來(lái)內(nèi)容中的某些標(biāo)簽的 id 或者 name 產(chǎn)生沖突。不過(guò)這里有一點(diǎn)要注意,這里也要判斷是否是 IE,然后再?zèng)Q定加不加這段內(nèi)容,因?yàn)槠渌承g覽器可能不支持 display: none 這個(gè) CSS 修飾(例如 Opera Mini),如果加上這段代碼會(huì)影響最終的顯示效果。
下面我們來(lái)看看如何取出腳本并執(zhí)行。
取出腳本很容易,只需要用 innerHTML 所在對(duì)象的 getElementsByTagName 方法就可以了,這個(gè)方法對(duì)幾乎所有的容器標(biāo)簽都管用。取出腳本以后,我們要一一判斷它們是外部腳本還是內(nèi)部腳本。
先來(lái)看外部腳本,如果是外部腳本,我們選擇了這樣一種方法,即先創(chuàng)建這個(gè)外部腳本的一個(gè)副本對(duì)象,并設(shè)置它的 defer 屬性為 true(這一點(diǎn)是為了讓 IE 瀏覽器能正確執(zhí)行),然后用 appendChild 方法將這個(gè)副本對(duì)象插入到 head 中。這里你可能會(huì)問(wèn),為什么不是插入到 innerHTML 所在的對(duì)象中呢?插入到 innerHTML 所在的對(duì)象中不是更好嗎?如果你試一下就會(huì)知道,如果插入到 innerHTML 所在的對(duì)象中,在 IE 瀏覽器中沒(méi)有問(wèn)題,但是在 Mozilla/Firefox 和 Opera 瀏覽器中會(huì)有一些問(wèn)題。問(wèn)題是如果在 Firefox 上這樣做,瀏覽器會(huì)停止響應(yīng)(這是在 Firefox 1.5 上的測(cè)試結(jié)果,其他版本是否有此問(wèn)題,尚不得知),而在 Opera 上,腳本會(huì)莫名其妙的執(zhí)行兩次(這是在 Opera 8.5 上的測(cè)試結(jié)果,其它版本的 Opera 是否由此問(wèn)題,也尚不得知)。為了避免這些問(wèn)題,所以我選擇了插入到 head 中。
再來(lái)看內(nèi)部腳本,內(nèi)部腳本的內(nèi)容我們可以直接用腳本對(duì)象的 text 屬性來(lái)獲取,這里我們使用腳本對(duì)象的 text 屬性而不是 innerHTML 屬性,是因?yàn)樵?Opera 瀏覽器中,腳本對(duì)象的 innerHTML 屬性是空的,只有用 text 屬性才能獲取到腳本內(nèi)容。執(zhí)行內(nèi)部腳本直接用 eval 即可。但是腳本可能會(huì)被包含在 HTML 的注釋標(biāo)簽中,因此我們需要先將注釋標(biāo)簽去掉,不然在 IE 中會(huì)出錯(cuò)。
上面的分析看上去很完美了,但是實(shí)際上還是有問(wèn)題,一個(gè)是 document.write 和 document.writeln 的問(wèn)題,這個(gè)問(wèn)題在 Blueidea 上,bound0 給出了一個(gè)思路,就是替換掉默認(rèn)的 document.write 和 document.writeln 方法,不過(guò)他用的是字符串替換,因此只對(duì)內(nèi)部腳本有效,對(duì)外部腳本就沒(méi)辦法了,因此我想了個(gè)更通用的辦法,就是直接把 document.write 和 document.writeln 重新定義,這樣不管內(nèi)部腳本還是外部腳本執(zhí)行的就都是我們我們自己定義的 document.write 和 document.writeln 了。不過(guò)也有副作用,就是這兩個(gè)函數(shù)在當(dāng)前頁(yè)面中就不能再像原來(lái)一樣使用了,不過(guò)這兩個(gè)函數(shù)在頁(yè)面加載完之后一般是不會(huì)再用到了,因此這里重新定義它們所帶來(lái)的副作用影響很小。但是還有個(gè)問(wèn)題是,盡管這樣,我們?nèi)匀粺o(wú)法保證 document.write 或 document.writeln 輸出的內(nèi)容會(huì)顯示在最合適的位置,它只是把內(nèi)容附加到了我們放置內(nèi)容的容器中。
另一個(gè)問(wèn)題是 eval 引起的問(wèn)題,一個(gè)是 Blueidea 上的 hutia 說(shuō)的作用域的問(wèn)題,另一個(gè)問(wèn)題是如果用 eval 執(zhí)行的內(nèi)部腳本的話,內(nèi)部腳本會(huì)在外部腳本加載完之前就開(kāi)始執(zhí)行了。要解決這個(gè)兩個(gè)問(wèn)題可以采用 window.setTimeout 這個(gè)函數(shù),讓每個(gè)腳本都延時(shí)一段后再執(zhí)行,外部腳本延時(shí)時(shí)間可以設(shè)的較長(zhǎng),以保證其能夠完全加載,而內(nèi)部腳本則可以設(shè)置為很短,因?yàn)橐粋€(gè)腳本執(zhí)行的時(shí)間通常是很短的,這樣既可以保證不會(huì)改變作用域,又可以基本保證腳本執(zhí)行順序不會(huì)改變了(這種方法對(duì)于保證執(zhí)行順序上也不一定會(huì) 100% 有效,如果網(wǎng)絡(luò)非常繁忙,外部腳本可能在設(shè)置的時(shí)間內(nèi)加載不完,但至少比直接用 eval 的時(shí)候好多了)。
如果按照前面的方式實(shí)現(xiàn),對(duì)于大多數(shù)腳本來(lái)說(shuō)可以正常執(zhí)行了。但是如果 script 中帶有 defer 屬性,IE 會(huì)自己運(yùn)行那段代碼(前面提過(guò)了),因此它會(huì)打亂執(zhí)行的順序。另外 document.write 和 document.writeln 寫(xiě)入的代碼都回被添加到最后面,而不是腳本所在的位置上,因此這也是個(gè)問(wèn)題。
為了解決這兩個(gè)問(wèn)題,我們需要對(duì)前面的解決方法作一些改變。首先我們不能先把內(nèi)容賦值給 innerHTML,然后再通過(guò)它取腳本了,我們需要直接對(duì)內(nèi)容分析來(lái)取出腳本。另外,腳本以外的 HTML 部分也不能直接賦值給 innerHTML,需要在腳本執(zhí)行以后,將原有的 HTML 內(nèi)容和 document.write\writeln 寫(xiě)的內(nèi)容按照順序合并到一起再賦值給 innerHTML,這里要注意,我們不能一部分一部分的將這些內(nèi)容連接到 innerHTML 后面,因?yàn)槠渲锌赡苡邪雮€(gè)標(biāo)簽的內(nèi)容,這種情況下,瀏覽器很容易發(fā)生錯(cuò)誤。而且你會(huì)看到頁(yè)面反復(fù)刷新的情況出現(xiàn)。而如果先放入緩沖區(qū),最后一次賦給 innerHTML,就不會(huì)出現(xiàn)這種問(wèn)題了。
另外放入緩沖區(qū)的好處是,當(dāng)腳本執(zhí)行完后,可以檢查緩沖區(qū)中是否還有新的腳本,如果有,再遞歸執(zhí)行,這樣就可以解決 document.write 和 document.writeln 寫(xiě)的腳本也可以執(zhí)行的問(wèn)題了。
2006-6-4 更新:
修正了插入到 innerHTML 中的腳本無(wú)法獲取插入到 innerHTML 中對(duì)象的問(wèn)題。(感謝網(wǎng)友 DE 的提醒)。
增加了對(duì)同一容器中內(nèi)容設(shè)置的共享鎖,使得連續(xù)設(shè)置同一個(gè)容器內(nèi)的時(shí),不會(huì)再發(fā)生沖突。(感謝新加坡網(wǎng)友 Jason Li 的提醒)。
2006-5-29 更新:
增加了使用外部腳本緩存功能,提高了第二次加載相同外部腳本的速度。
2006-5-23 更新:
在熱心的使用者 johnZEN 的提醒下,增加了共享鎖,使得同時(shí)設(shè)置多個(gè)容器內(nèi)的內(nèi)容時(shí),不會(huì)再發(fā)生沖突。
在網(wǎng)友 udbjatwfn 的提醒下,修正了 IE 中存在的內(nèi)部腳本執(zhí)行作用域錯(cuò)誤的問(wèn)題。
下面是最后本人的實(shí)現(xiàn)代碼:
/* innerhtml.js
* Copyright Ma Bingyao <andot@ujn.edu.cn>
* Version: 1.9
* LastModified: 2006-06-04
* This library is free. You can redistribute it and/or modify it.
* http://www.coolcode.cn/?p=117
*/
var global_html_pool = [];
var global_script_pool = [];
var global_script_src_pool = [];
var global_lock_pool = [];
var innerhtml_lock = null;
var document_buffer = "";
function set_innerHTML(obj_id, html, time) {
if (innerhtml_lock == null) {
innerhtml_lock = obj_id;
}
else if (typeof(time) == "undefined") {
global_lock_pool[obj_id + "_html"] = html;
window.setTimeout("set_innerHTML('" + obj_id + "', global_lock_pool['" + obj_id + "_html']);", 10);
return;
}
else if (innerhtml_lock != obj_id) {
global_lock_pool[obj_id + "_html"] = html;
window.setTimeout("set_innerHTML('" + obj_id + "', global_lock_pool['" + obj_id + "_html'], " + time + ");", 10);
return;
}
function get_script_id() {
return "script_" + (new Date()).getTime().toString(36)
+ Math.floor(Math.random() * 100000000).toString(36);
}
document_buffer = "";
document.write = function (str) {
document_buffer += str;
}
document.writeln = function (str) {
document_buffer += str + "\n";
}
global_html_pool = [];
var scripts = [];
html = html.split(/<\/script>/i);
for (var i = 0; i < html.length; i++) {
global_html_pool[i] = html[i].replace(/<script[\s\S]*$/ig, "");
scripts[i] = {text: '', src: '' };
scripts[i].text = html[i].substr(global_html_pool[i].length);
scripts[i].src = scripts[i].text.substr(0, scripts[i].text.indexOf('>') + 1);
scripts[i].src = scripts[i].src.match(/src\s*=\s*(\"([^\"]*)\"|\'([^\']*)\'|([^\s]*)[\s>])/i);
if (scripts[i].src) {
if (scripts[i].src[2]) {
scripts[i].src = scripts[i].src[2];
}
else if (scripts[i].src[3]) {
scripts[i].src = scripts[i].src[3];
}
else if (scripts[i].src[4]) {
scripts[i].src = scripts[i].src[4];
}
else {
scripts[i].src = "";
}
scripts[i].text = "";
}
else {
scripts[i].src = "";
scripts[i].text = scripts[i].text.substr(scripts[i].text.indexOf('>') + 1);
scripts[i].text = scripts[i].text.replace(/^\s*<\!--\s*/g, "");
}
}
var s;
if (typeof(time) == "undefined") {
s = 0;
}
else {
s = time;
}
var script, add_script, remove_script;
for (var i = 0; i < scripts.length; i++) {
var add_html = "document_buffer += global_html_pool[" + i + "];\n";
add_html += "document.getElementById('" + obj_id + "').innerHTML = document_buffer;\n";
script = document.createElement("script");
if (scripts[i].src) {
script.src = scripts[i].src;
if (typeof(global_script_src_pool[script.src]) == "undefined") {
global_script_src_pool[script.src] = true;
s += 2000;
}
else {
s += 10;
}
}
else {
script.text = scripts[i].text;
s += 10;
}
script.defer = true;
script.type = "text/javascript";
script.id = get_script_id();
global_script_pool[script.id] = script;
add_script = add_html;
add_script += "document.getElementsByTagName('head').item(0)";
add_script += ".appendChild(global_script_pool['" + script.id + "']);\n";
window.setTimeout(add_script, s);
remove_script = "document.getElementsByTagName('head').item(0)";
remove_script += ".removeChild(document.getElementById('" + script.id + "'));\n";
remove_script += "delete global_script_pool['" + script.id + "'];\n";
window.setTimeout(remove_script, s + 10000);
}
var end_script = "if (document_buffer.match(/<\\/script>/i)) {\n";
end_script += "set_innerHTML('" + obj_id + "', document_buffer, " + s + ");\n";
end_script += "}\n";
end_script += "else {\n";
end_script += "document.getElementById('" + obj_id + "').innerHTML = document_buffer;\n";
end_script += "innerhtml_lock = null;\n";
end_script += "}";
window.setTimeout(end_script, s);
}
JS調(diào)用方法:
JavaScript代碼
set_innerHTML('要插入innerhtml的ID名稱', '要插入的代碼');
方案2:來(lái)自ajaxwing的innerHTML簡(jiǎn)單版
不過(guò)這個(gè)實(shí)現(xiàn)有一點(diǎn)問(wèn)題就是腳本中的 document.write 和 document.writeln 縮寫(xiě)的內(nèi)容位置是不對(duì)的。
調(diào)用方法:
JavaScript代碼
setInnerHTML('DOM 樹(shù)中的節(jié)點(diǎn)', '要插入的代碼');
JavaScript代碼
/*
* 描述:跨瀏覽器的設(shè)置 innerHTML 方法
* 允許插入的 HTML 代碼中包含 script 和 style
* 作者:kenxu <ken@ajaxwing.com>
* 日期:2006-03-23
* 參數(shù):
* el: 合法的 DOM 樹(shù)中的節(jié)點(diǎn)
* htmlCode: 合法的 HTML 代碼
* 經(jīng)測(cè)試的瀏覽器: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);
}
}
}
基于原作者的不讓轉(zhuǎn)載,導(dǎo)致所有的測(cè)試代碼都沒(méi)有了,本來(lái)應(yīng)該留一份的。唉
不過(guò)腳本之家特為大家制作了一個(gè)例子,以后大家可以全站的使用js控制廣告,減少連接數(shù)。
http://www.dbjr.com.cn/article/20068.htm
相關(guān)文章
yolov5項(xiàng)目部署+微信小程序前端展示的全過(guò)程
YOLOV5模型從發(fā)布到現(xiàn)在都是炙手可熱的目標(biāo)檢測(cè)模型,被廣泛運(yùn)用于各大場(chǎng)景之中,下面這篇文章主要給大家介紹了關(guān)于yolov5項(xiàng)目部署+微信小程序前端展示的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11JavaScript算法學(xué)習(xí)之冒泡排序和選擇排序
這篇文章主要給大家介紹了關(guān)于JavaScript算法學(xué)習(xí)之冒泡排序和選擇排序的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用JavaScript具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11如何用JS實(shí)現(xiàn)簡(jiǎn)單的數(shù)據(jù)監(jiān)聽(tīng)
這篇文章主要介紹了如何用JS實(shí)現(xiàn)簡(jiǎn)單的數(shù)據(jù)監(jiān)聽(tīng),對(duì)數(shù)據(jù)監(jiān)聽(tīng)感興趣的同學(xué),可以參考一下2021-05-05前端處理二進(jìn)制流文件導(dǎo)出為excel表代碼示例
這篇文章主要給大家介紹了關(guān)于前端處理二進(jìn)制流文件導(dǎo)出為excel表的相關(guān)資料,后臺(tái)管理系統(tǒng),常會(huì)出現(xiàn)導(dǎo)出excel表格功能,需要的朋友可以參考下2023-08-08微信小程序 自定義彈窗實(shí)現(xiàn)過(guò)程(附代碼)
這篇文章主要介紹了微信小程序 自定義彈窗實(shí)現(xiàn)過(guò)程(附代碼),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12微信小程序?qū)W習(xí)筆記之頁(yè)面配置與路由方式
這篇文章主要給大家介紹了關(guān)于微信小程序?qū)W習(xí)筆記之頁(yè)面配置與路由方式的相關(guān)資料,頁(yè)面配置和路由是學(xué)習(xí)微信小程序必然會(huì)遇到的,本文通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-06-06解決layer 動(dòng)態(tài)加載select 失效的問(wèn)題
今天小編就為大家分享一篇解決layer 動(dòng)態(tài)加載select 失效的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09原生js實(shí)現(xiàn)可拖動(dòng)的登錄框效果
本文主要介紹了原生js實(shí)現(xiàn)可拖動(dòng)的登錄框效果的示例代碼。具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-01-01Javascript中JSON數(shù)據(jù)分組優(yōu)化實(shí)踐及JS操作JSON總結(jié)
這篇文章主要介紹了Javascript中JSON數(shù)據(jù)分組優(yōu)化實(shí)踐,文中還對(duì)JS操作JSON的要領(lǐng)做了總結(jié),需要的朋友可以參考下2017-12-12