執行AJAX返回HTML片段中的JavaScript腳本


如果AJAX加載的數據是一個HTML片段,而且這個HTML片段還包含腳本<script>塊,那么在你把這數據xmlHttp.responseText用innerHTML方法插入到當前文檔一個元素中,你會發現AJAX加載回來的腳本根本沒有執行。這是AJAX開發中很常見的問題,如果你不是一直在用JavaScript框架做開發,相信你早就發現這個問題了。本文分析了兩個解決辦法,其中一個是講解jQuery框架的實現。

一、 問題描述

下面舉個簡單的例子,演示問題所在。在下面的例子中,假設變量responseText就是AJAX加載的HTML片段數據,其中包含腳本彈出一條消息,用innerHTML方法插入ID為ajaxData的DIV中,你可能期望看到彈出那個消息框,結果你發現沒有,問題就是這樣。

<div id="ajaxData"></div>
<script type="text/javascript">var=';
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;//匹配正則表達式的內容數組,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 + 10].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)
    elem.parentNode.removeChild(elem);

最后,在globalEval方法中,發現head.removeChild( script );方法,就是把腳本插入head后馬上又移除腳本標簽,這也是避免因為重復執行html(value)方法在head區生成重復的腳本塊。這個移除是不影響腳本執行的,同是也是不會清除腳本塊中的相關變量值。顯然,如果你想看看html(data)最終的執行結果,比如抽取后插入到head的腳本塊是什么,你可以先臨時注釋這一行代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM