這是AJAX開發中很常見的問題,如果你不是一直在用JavaScript架構做開發,相信你早就發現這個問題了。本文分析了兩個解決辦法,其中一個是講解jQuery架構的實現。
一、 問題描述
下面舉個簡單的例子,示範問題所在。在下面的例子中,假設變數responseText就是AJAX載入的HTML片段資料,其中包含指令碼彈出一條訊息,用innerHTML方法插入ID為ajaxData的DIV中,你可能期望看到彈出那個訊息框,結果你發現沒有,問題就是這樣。 複製代碼 代碼如下:<div id="ajaxData"></div>
<script type="text/javascript">
var responseText = '<p>這是一個段落</p><script>alert("這是AJAX載入回來的指令碼片段")</script>';
document.getElementById('ajaxData').innerHTML = responseText;
</script>
二、兩種解決辦法
1、 利用JavaScript的eval方法執行指令碼。
本方法的具體實現思路是把xmlHttp.responseText中的指令碼都抽取出來,不管AJAX載入的HTML包含多少個指令碼塊,我們對找出來的指令碼塊都調用eval方法執行它即可。下面提供一個封裝好的函數: 複製代碼 代碼如下:function executeScript(html)
{
var reg = /<script[^>]*>([^\x00]+)$/i;
//對整段HTML片段按<\/script>拆分
var htmlBlock = html.split("<\/script>");
for (var i in htmlBlock)
{
var blocks;//匹配Regex的內容數組,blocks[1]就是真正的一段指令碼內容,因為前面reg定義我們用了括弧進行了捕獲分組
if (blocks = htmlBlock[i].match(reg))
{
//清除可能存在的注釋標記,對於注釋結尾-->可以忽略處理,eval一樣能正常工作
var code = blocks[1].replace(/<!--/, '');
try
{
eval(code) //執行指令碼
}
catch (e)
{
}
}
}
}
本方法的使用如下,對HTML用innerHTML方法添加到DOM,緊跟著調用executeScript方法執行指令碼塊: 複製代碼 代碼如下:document.getElementById("div1").innerHTML = xmlHttp.responseText;
executeScript(xmlHttp.responseText);
顯然這個方法還是存在缺陷的,如果xmlHttp.responseText包含像這樣的外部指令碼調用:
<script type="text/javascript" src="/js/common.js"></script>,executeScript方法不能再深入執行這個外部載入的指令碼。
2、 學習並使用jQuery架構的實現
jQuery對於AJAX載入HTML,是最終在執行html(value)方法時把整個xmlHttp.responseText資料轉換成DOM,然後利用DOM相關操作方法來找出裡面的指令碼,最後再把這些指令碼插入到head中。具體原理也不好說,先舉個最簡單的例子,然後再分析一下大致思路。先看例子: 複製代碼 代碼如下:$.get('ajax.aspx', function(data)
{
$('#div1').html(data);
});
現在假設上面ajax.aspx頁面返回的是HTML片段,而且包含一個或多個指令碼塊,甚至外部指令碼引用。div1是AJAX請求發起頁的一個DIV標籤的ID,整句代碼實現的結果是載入ajax.aspx中的HTML填充到一個ID為div1的DIV標籤中。
在匿名回呼函數中通過typeof(data)可以發現data還是原始的字串,即等同於xmlHttp.responseText,通過代碼執行跟蹤發現,對AJAX載入指令碼片段的執行處理不在jQuery的AJAX模組代碼中,而是在html(value)方法,即把一段包含指令碼塊的HTML字串插入DOM時,由它負責抽出指令碼進行調用處理。而html(value)方法其實又是調用了append(value)方法……,整個過程大概調用了以下方法,箭頭代表調用這些方法的先後順序:
html -> append -> domManip -> clean -> evalScript -> globalEval
其中clean方法特別關鍵,這個方法也是jQuery比較重要的方法,其中也涉及修複HTML錯誤(標籤沒有結束,表格結構調整等方法)處理指令碼。而指令碼的抽出也是在這裡進行的。看看相關原始碼(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方法中我們還發現如下代碼,這裡是“同”步載入像<script type="text/javascript" src="/js/common.js"></script>這樣的外部指令碼,解決executeScript方法存在的一個缺陷: 複製代碼 代碼如下:if (elem.src)
jQuery.ajax(
{
url: elem.src,
async: false,
dataType: "script"
});
同時也發現如下代碼,這段代碼是把xmlHttp.responseText中的指令碼刪除,因為在這個方法中,jQuery是準備把抽取的指令碼放入head區,所以刪除可以避免最終的HTML出現重複的指令碼塊: 複製代碼 代碼如下:if (elem.parentNode)
最後,在globalEval方法中,發現head.removeChild( script );方法,就是把指令碼插入head後馬上又移除指令碼標籤,這也是避免因為重複執行html(value)方法在head區產生重複的指令碼塊。這個移除是不影響指令碼執行的,同是也是不會清除指令碼塊中的相關變數值。顯然,如果你想看看html(data)最終的執行結果,比如抽取後插入到head的指令碼塊是什麼,你可以先臨時注釋這一行代碼。