用AJAX返回HTML片段中的JavaScript腳本
更新時(shí)間:2010年01月04日 21:48:55 作者:
如果AJAX加載的數(shù)據(jù)是一個(gè)HTML片段,而且這個(gè)HTML片段還包含腳本<script>塊,那么在你把這數(shù)據(jù)xmlHttp.responseText用innerHTML方法插入到當(dāng)前文檔一個(gè)元素中,你會(huì)發(fā)現(xiàn)AJAX加載回來(lái)的腳本根本沒(méi)有執(zhí)行。
這是AJAX開(kāi)發(fā)中很常見(jiàn)的問(wèn)題,如果你不是一直在用JavaScript框架做開(kāi)發(fā),相信你早就發(fā)現(xiàn)這個(gè)問(wèn)題了。本文分析了兩個(gè)解決辦法,其中一個(gè)是講解jQuery框架的實(shí)現(xiàn)。
一、 問(wèn)題描述
下面舉個(gè)簡(jiǎn)單的例子,演示問(wèn)題所在。在下面的例子中,假設(shè)變量responseText就是AJAX加載的HTML片段數(shù)據(jù),其中包含腳本彈出一條消息,用innerHTML方法插入ID為ajaxData的DIV中,你可能期望看到彈出那個(gè)消息框,結(jié)果你發(fā)現(xiàn)沒(méi)有,問(wèn)題就是這樣。
<div id="ajaxData"></div>
<script type="text/javascript">
var responseText = '<p>這是一個(gè)段落</p><script>alert("這是AJAX加載回來(lái)的腳本片段")</script>';
document.getElementById('ajaxData').innerHTML = responseText;
</script>
二、兩種解決辦法
1、 利用JavaScript的eval方法執(zhí)行腳本。
本方法的具體實(shí)現(xiàn)思路是把xmlHttp.responseText中的腳本都抽取出來(lái),不管AJAX加載的HTML包含多少個(gè)腳本塊,我們對(duì)找出來(lái)的腳本塊都調(diào)用eval方法執(zhí)行它即可。下面提供一個(gè)封裝好的函數(shù):
function executeScript(html)
{
var reg = /<script[^>]*>([^\x00]+)$/i;
//對(duì)整段HTML片段按<\/script>拆分
var htmlBlock = html.split("<\/script>");
for (var i in htmlBlock)
{
var blocks;//匹配正則表達(dá)式的內(nèi)容數(shù)組,blocks[1]就是真正的一段腳本內(nèi)容,因?yàn)榍懊鎟eg定義我們用了括號(hào)進(jìn)行了捕獲分組
if (blocks = htmlBlock[i].match(reg))
{
//清除可能存在的注釋標(biāo)記,對(duì)于注釋結(jié)尾-->可以忽略處理,eval一樣能正常工作
var code = blocks[1].replace(/<!--/, '');
try
{
eval(code) //執(zhí)行腳本
}
catch (e)
{
}
}
}
}
本方法的使用如下,對(duì)HTML用innerHTML方法添加到DOM,緊跟著調(diào)用executeScript方法執(zhí)行腳本塊:
document.getElementById("div1").innerHTML = xmlHttp.responseText;
executeScript(xmlHttp.responseText);
顯然這個(gè)方法還是存在缺陷的,如果xmlHttp.responseText包含像這樣的外部腳本調(diào)用:
<script type="text/javascript" src="/js/common.js"></script>,executeScript方法不能再深入執(zhí)行這個(gè)外部加載的腳本。
2、 學(xué)習(xí)并使用jQuery框架的實(shí)現(xiàn)
jQuery對(duì)于AJAX加載HTML,是最終在執(zhí)行html(value)方法時(shí)把整個(gè)xmlHttp.responseText數(shù)據(jù)轉(zhuǎn)換成DOM,然后利用DOM相關(guān)操作方法來(lái)找出里面的腳本,最后再把這些腳本插入到head中。具體原理也不好說(shuō),先舉個(gè)最簡(jiǎn)單的例子,然后再分析一下大致思路。先看例子:
$.get('ajax.aspx', function(data)
{
$('#div1').html(data);
});
現(xiàn)在假設(shè)上面ajax.aspx頁(yè)面返回的是HTML片段,而且包含一個(gè)或多個(gè)腳本塊,甚至外部腳本引用。div1是AJAX請(qǐng)求發(fā)起頁(yè)的一個(gè)DIV標(biāo)簽的ID,整句代碼實(shí)現(xiàn)的結(jié)果是加載ajax.aspx中的HTML填充到一個(gè)ID為div1的DIV標(biāo)簽中。
在匿名回調(diào)函數(shù)中通過(guò)typeof(data)可以發(fā)現(xiàn)data還是原始的字符串,即等同于xmlHttp.responseText,通過(guò)代碼執(zhí)行跟蹤發(fā)現(xiàn),對(duì)AJAX加載腳本片段的執(zhí)行處理不在jQuery的AJAX模塊代碼中,而是在html(value)方法,即把一段包含腳本塊的HTML字符串插入DOM時(shí),由它負(fù)責(zé)抽出腳本進(jìn)行調(diào)用處理。而html(value)方法其實(shí)又是調(diào)用了append(value)方法……,整個(gè)過(guò)程大概調(diào)用了以下方法,箭頭代表調(diào)用這些方法的先后順序:
html -> append -> domManip -> clean -> evalScript -> globalEval
其中clean方法特別關(guān)鍵,這個(gè)方法也是jQuery比較重要的方法,其中也涉及修復(fù)HTML錯(cuò)誤(標(biāo)簽沒(méi)有結(jié)束,表格結(jié)構(gòu)調(diào)整等方法)處理腳本。而腳本的抽出也是在這里進(jìn)行的??纯聪嚓P(guān)源代碼(jQuery1.3.2):
if (fragment)
{
for (var i = 0; ret[i]; i++)
{
if (jQuery.nodeName(ret[i], "script") && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript"))
{
scripts.push(ret[i].parentNode ? ret[i].parentNode.removeChild(ret[i]) : ret[i]);
}
else
{
if (ret[i].nodeType === 1)
ret.splice.apply(ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))));
fragment.appendChild(ret[i]);
}
}
return scripts;
}
另外,在evalScript方法中我們還發(fā)現(xiàn)如下代碼,這里是“同”步加載像<script type="text/javascript" src="/js/common.js"></script>這樣的外部腳本,解決executeScript方法存在的一個(gè)缺陷:
if (elem.src)
jQuery.ajax(
{
url: elem.src,
async: false,
dataType: "script"
});
同時(shí)也發(fā)現(xiàn)如下代碼,這段代碼是把xmlHttp.responseText中的腳本刪除,因?yàn)樵谶@個(gè)方法中,jQuery是準(zhǔn)備把抽取的腳本放入head區(qū),所以刪除可以避免最終的HTML出現(xiàn)重復(fù)的腳本塊:
if (elem.parentNode)
最后,在globalEval方法中,發(fā)現(xiàn)head.removeChild( script );方法,就是把腳本插入head后馬上又移除腳本標(biāo)簽,這也是避免因?yàn)橹貜?fù)執(zhí)行html(value)方法在head區(qū)生成重復(fù)的腳本塊。這個(gè)移除是不影響腳本執(zhí)行的,同是也是不會(huì)清除腳本塊中的相關(guān)變量值。顯然,如果你想看看html(data)最終的執(zhí)行結(jié)果,比如抽取后插入到head的腳本塊是什么,你可以先臨時(shí)注釋這一行代碼。
一、 問(wèn)題描述
下面舉個(gè)簡(jiǎn)單的例子,演示問(wèn)題所在。在下面的例子中,假設(shè)變量responseText就是AJAX加載的HTML片段數(shù)據(jù),其中包含腳本彈出一條消息,用innerHTML方法插入ID為ajaxData的DIV中,你可能期望看到彈出那個(gè)消息框,結(jié)果你發(fā)現(xiàn)沒(méi)有,問(wèn)題就是這樣。
復(fù)制代碼 代碼如下:
<div id="ajaxData"></div>
<script type="text/javascript">
var responseText = '<p>這是一個(gè)段落</p><script>alert("這是AJAX加載回來(lái)的腳本片段")</script>';
document.getElementById('ajaxData').innerHTML = responseText;
</script>
二、兩種解決辦法
1、 利用JavaScript的eval方法執(zhí)行腳本。
本方法的具體實(shí)現(xiàn)思路是把xmlHttp.responseText中的腳本都抽取出來(lái),不管AJAX加載的HTML包含多少個(gè)腳本塊,我們對(duì)找出來(lái)的腳本塊都調(diào)用eval方法執(zhí)行它即可。下面提供一個(gè)封裝好的函數(shù):
復(fù)制代碼 代碼如下:
function executeScript(html)
{
var reg = /<script[^>]*>([^\x00]+)$/i;
//對(duì)整段HTML片段按<\/script>拆分
var htmlBlock = html.split("<\/script>");
for (var i in htmlBlock)
{
var blocks;//匹配正則表達(dá)式的內(nèi)容數(shù)組,blocks[1]就是真正的一段腳本內(nèi)容,因?yàn)榍懊鎟eg定義我們用了括號(hào)進(jìn)行了捕獲分組
if (blocks = htmlBlock[i].match(reg))
{
//清除可能存在的注釋標(biāo)記,對(duì)于注釋結(jié)尾-->可以忽略處理,eval一樣能正常工作
var code = blocks[1].replace(/<!--/, '');
try
{
eval(code) //執(zhí)行腳本
}
catch (e)
{
}
}
}
}
本方法的使用如下,對(duì)HTML用innerHTML方法添加到DOM,緊跟著調(diào)用executeScript方法執(zhí)行腳本塊:
復(fù)制代碼 代碼如下:
document.getElementById("div1").innerHTML = xmlHttp.responseText;
executeScript(xmlHttp.responseText);
顯然這個(gè)方法還是存在缺陷的,如果xmlHttp.responseText包含像這樣的外部腳本調(diào)用:
<script type="text/javascript" src="/js/common.js"></script>,executeScript方法不能再深入執(zhí)行這個(gè)外部加載的腳本。
2、 學(xué)習(xí)并使用jQuery框架的實(shí)現(xiàn)
jQuery對(duì)于AJAX加載HTML,是最終在執(zhí)行html(value)方法時(shí)把整個(gè)xmlHttp.responseText數(shù)據(jù)轉(zhuǎn)換成DOM,然后利用DOM相關(guān)操作方法來(lái)找出里面的腳本,最后再把這些腳本插入到head中。具體原理也不好說(shuō),先舉個(gè)最簡(jiǎn)單的例子,然后再分析一下大致思路。先看例子:
復(fù)制代碼 代碼如下:
$.get('ajax.aspx', function(data)
{
$('#div1').html(data);
});
現(xiàn)在假設(shè)上面ajax.aspx頁(yè)面返回的是HTML片段,而且包含一個(gè)或多個(gè)腳本塊,甚至外部腳本引用。div1是AJAX請(qǐng)求發(fā)起頁(yè)的一個(gè)DIV標(biāo)簽的ID,整句代碼實(shí)現(xiàn)的結(jié)果是加載ajax.aspx中的HTML填充到一個(gè)ID為div1的DIV標(biāo)簽中。
在匿名回調(diào)函數(shù)中通過(guò)typeof(data)可以發(fā)現(xiàn)data還是原始的字符串,即等同于xmlHttp.responseText,通過(guò)代碼執(zhí)行跟蹤發(fā)現(xiàn),對(duì)AJAX加載腳本片段的執(zhí)行處理不在jQuery的AJAX模塊代碼中,而是在html(value)方法,即把一段包含腳本塊的HTML字符串插入DOM時(shí),由它負(fù)責(zé)抽出腳本進(jìn)行調(diào)用處理。而html(value)方法其實(shí)又是調(diào)用了append(value)方法……,整個(gè)過(guò)程大概調(diào)用了以下方法,箭頭代表調(diào)用這些方法的先后順序:
html -> append -> domManip -> clean -> evalScript -> globalEval
其中clean方法特別關(guān)鍵,這個(gè)方法也是jQuery比較重要的方法,其中也涉及修復(fù)HTML錯(cuò)誤(標(biāo)簽沒(méi)有結(jié)束,表格結(jié)構(gòu)調(diào)整等方法)處理腳本。而腳本的抽出也是在這里進(jìn)行的??纯聪嚓P(guān)源代碼(jQuery1.3.2):
復(fù)制代碼 代碼如下:
if (fragment)
{
for (var i = 0; ret[i]; i++)
{
if (jQuery.nodeName(ret[i], "script") && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript"))
{
scripts.push(ret[i].parentNode ? ret[i].parentNode.removeChild(ret[i]) : ret[i]);
}
else
{
if (ret[i].nodeType === 1)
ret.splice.apply(ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))));
fragment.appendChild(ret[i]);
}
}
return scripts;
}
另外,在evalScript方法中我們還發(fā)現(xiàn)如下代碼,這里是“同”步加載像<script type="text/javascript" src="/js/common.js"></script>這樣的外部腳本,解決executeScript方法存在的一個(gè)缺陷:
復(fù)制代碼 代碼如下:
if (elem.src)
jQuery.ajax(
{
url: elem.src,
async: false,
dataType: "script"
});
同時(shí)也發(fā)現(xiàn)如下代碼,這段代碼是把xmlHttp.responseText中的腳本刪除,因?yàn)樵谶@個(gè)方法中,jQuery是準(zhǔn)備把抽取的腳本放入head區(qū),所以刪除可以避免最終的HTML出現(xiàn)重復(fù)的腳本塊:
復(fù)制代碼 代碼如下:
if (elem.parentNode)
最后,在globalEval方法中,發(fā)現(xiàn)head.removeChild( script );方法,就是把腳本插入head后馬上又移除腳本標(biāo)簽,這也是避免因?yàn)橹貜?fù)執(zhí)行html(value)方法在head區(qū)生成重復(fù)的腳本塊。這個(gè)移除是不影響腳本執(zhí)行的,同是也是不會(huì)清除腳本塊中的相關(guān)變量值。顯然,如果你想看看html(data)最終的執(zhí)行結(jié)果,比如抽取后插入到head的腳本塊是什么,你可以先臨時(shí)注釋這一行代碼。
相關(guān)文章
用Node.js通過(guò)sitemap.xml批量抓取美女圖片
這篇文章主要介紹了用Node.js通過(guò)sitemap.xml批量抓取美女圖片的方法和相關(guān)代碼,有需要的小伙伴可以參考下。2015-05-05
用js統(tǒng)計(jì)用戶下載網(wǎng)頁(yè)所需時(shí)間的腳本
下面的方法是個(gè)不錯(cuò)的思路,建議對(duì)于js感興趣的朋友,推薦看2008-10-10
JS中g(shù)etElementsByClassName與classList兼容性問(wèn)題解決方案分析
這篇文章主要介紹了JS中g(shù)etElementsByClassName與classList兼容性問(wèn)題解決方案,結(jié)合實(shí)例形式分析了getElementsByClassName與classList的使用方法、原理及兼容性問(wèn)題的處理技巧,需要的朋友可以參考下2019-08-08
Select標(biāo)簽下拉列表二級(jí)聯(lián)動(dòng)級(jí)聯(lián)實(shí)例代碼
這篇文章主要介紹了Select標(biāo)簽下拉列表二級(jí)聯(lián)動(dòng)級(jí)聯(lián)實(shí)例代碼,需要的朋友可以參考下2014-02-02
easyui-combobox 實(shí)現(xiàn)簡(jiǎn)單的自動(dòng)補(bǔ)全功能示例
下面小編就為大家?guī)?lái)一篇easyui-combobox 實(shí)現(xiàn)簡(jiǎn)單的自動(dòng)補(bǔ)全功能示例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,2016-11-11
javaScript對(duì)文字按照拼音排序?qū)崿F(xiàn)代碼
這篇文章主要介紹了javaScript對(duì)文字按照拼音排序?qū)崿F(xiàn)代碼,有需要的朋友可以參考一下2013-12-12
純javascript響應(yīng)式樹(shù)形菜單效果
這篇文章主要為大家分享了純javascript響應(yīng)式樹(shù)形菜單效果的簡(jiǎn)單教程,對(duì)多級(jí)目錄樹(shù)形菜單感興趣的小伙伴們可以參考一下2015-11-11

