在ajax调用之后,脚本加载在哪里?

6
假设您有一个简单的网页,动态加载的内容如下所示:

- main.html -

<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript"
   src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-1.6.2.js">
</script>
<script type="text/javascript">
$(function() {
    $.ajax({
         type: 'get', cache: false, url: '/svc.html',
         success: function(h) {
             $('#main').html(h);
         }
    });
});
</script>
</head>

<body>
    <div id='main'>
    loading...
    </div>
</body>
</html>

并且加载的页面使用一个单独文件中的少量Javascript代码:

- svc.html -

<script type="text/javascript"
   src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-1.6.2.js">
</script>
<script type="text/javascript" src="/plugin.js" css="/plugin.css"></script>
<div id='inner'>
dynamically loaded content
</div>

请注意脚本标签上的css属性 - 它指示属于该脚本的样式表,该脚本将为我们加载。以下是脚本内容:
- plugin.js -
var css = $('<link />', {
    rel: "stylesheet", type: "text/css", href: $('script:last').attr('css')
});
$('head').append(css);

最后,样式表仅为加载的inner div着色,以证明一切正常:

- plugin.css -

#inner
{
    border: solid 1px blue;
}

现在,您会注意到样式表的加载方式:我们查看$('script')并拿走最后一个,然后我们获取css属性并将一个link项附加到文档头部。
我可以访问/svc.html,JavaScript运行,加载我的样式表,一切正常 - 但是,如果我访问/main.html,JavaScript运行但无法在$('script')数组中找到plugin.js的加载(因此无法加载样式表)。请注意,插件脚本确实运行,只是它没有看到自己。
这是一个错误吗? jQuery AJAX方法的限制吗? $('script')不应该反映已加载的所有脚本吗?
*编辑*
经过长时间的哀号和咬牙切齿,我决定采用Kevin B的解决方案,但是我无法使其工作,基本上是因为plugin.js代码在插入scriptNode时运行,但在插件代码内部,节点尚未被插入,因此在$('script')数组中不可用 - 所以我仍然处于困境中。我把所有的代码都放在这里供审查:http://www.arix.com/tmp/loadWithJs/
5个回答

5
jQuery代码可以向DOM添加HTML,但通常会删除其中的<script>标签,并执行这些标签中包含的脚本。一个例外情况是使用"$.load()"方法并搭配一种技巧来加载页面片段时,这时会保留<script>标签并执行其中的脚本。
$.load("http://something.com/whatever #stuff_I_want", function() { ... });

在这种情况下,脚本会被剥离且不会被评估或运行。

哦...太糟糕了。我已经测试了$('#main').load()方法,它似乎表现相同。它确实运行脚本,但也将其丢弃... - ekkis
你对我给Kevin B的问题有什么想法吗? - ekkis
好吧...他们做出了决定,我们必须接受它;但我陷入这个困境的原因是因为我想将样式表与脚本关联起来,并使脚本负责加载它。你能想到另一种实现这个目标的方法吗?因为我无法访问脚本数组。或者换句话说,我如何在脚本中访问加载脚本的标签? - ekkis
2
这是一件难事。我认为这可能需要退后一步,尝试提出一个完全不同的替代策略。 - Pointy
根据我的经验,仅仅使用Internet Explorer无法识别通过ajax加载的子页面的外部css。样式必须在被加载的页面显式声明或者...在调用页面中声明。 - zequinha-bsb
显示剩余6条评论

1
你可以使用 $.ajax 来获取 HTML,自己剥离脚本标签,将内容附加到 DOM 中的位置,然后将脚本标签附加到你想要它执行的位置,以便按照你想要的方式执行。
$.ajax({...}).done(function(html){
  var htmlToAppend = html;

  // replace all script tags with divs
  htmlToAppend.replace(/<script/ig,"<div class='script'");
  htmlToAppend.replace(/<\/script>/ig,"</div>");

  // detach script divs from html
  htmlToAppend = $(htmlToAppend);
  var scripts = htmlToAppend.find(".script").detach();

  // append html to target div
  htmlToAppend.appendTo("#target");

  // loop through scripts and append them to the dom
  scripts.each(function(){
    var $script = $(this), 
        scriptText = $script.html(), 
        scriptNode = document.createElement('script');

    $(scriptNode).attr('css', $script.attr('css');
    scriptNode.appendChild(document.createTextNode(scriptText));
    $("#target")[0].appendChild(scriptNode);
  });

});

我还没有测试过这段代码,但它是基于history.js的代码。

编辑:这里有一个更简单的解决方案,更适合你的需求:

$("#target").load(url,function(obj){
    // get css attribute from script tag using the raw returned html
    var cssPath = obj.responseText.match(/css="(.*)"/i)[1];
    $('<link />', {
        rel: "stylesheet", type: "text/css", href: cssPath
    }).appendTo('head');

});

由于 .load() 方法会移除脚本标签并执行其中的内容,所以这里我们需要读取原始响应文本,并从中获取 CSS 路径,然后添加链接元素。

我不完全确定为什么要这样处理。

编辑:请参阅评论。

$.ajax({...}).done(function(html){
  $("#target")[0].innerHTML = html;
});

是的,您提出的解决方案可行。问题在于我希望在调用的脚本内处理它,这样调用者就不需要负责这样的特殊处理了。 - ekkis
我对你最后的编辑感到兴奋,但它并不起作用。实际上,这篇博客文章:http://poeticcode.wordpress.com/2007/10/03/innerhtml-and-script-tags/#comment-8765 谈到了这个问题,超出了jQuery的范畴,基本上等同于你的第一个建议。我已经关闭了这个问题,并表示感谢,因为我认为这是唯一的解决方案。 - ekkis
我重新打开了这个问题,因为我无法让你的解决方案工作。我已经把所有的代码放在这里:http://www.arix.com/tmp/loadWithJs/,希望你能看一下。基本上,`plugin.js`中的代码在附加scriptNode时运行(我附加到head)- 但是,在那个时候,节点还没有被附加,因此在$('script')数组中还不可用 - 所以我仍然很困扰。 - ekkis
看看我的代码,第一个片段。我做了一个小改变 $('head')[0].appendChild(scriptNode); - Kevin B
在一个奇怪的事件中,.appendChild()通过推迟脚本的执行来解决标签尚不存在的问题,但会导致其中定义的函数不立即可用(参见http://stackoverflow.com/questions/8283360/where-are-scripts-loaded-after-an-ajax-call-redux)。另一方面,JQuery的.append()立即执行脚本,因此解决了我的第二个问题。所以看起来我很惨!我不能两全其美 :( - ekkis
显示剩余3条评论

1

我的解决方案会相当简单

为什么不在主函数中一次性加载所需的文件呢?然后只需要添加一个监听事件(检查内容是否已经加载完成)。

唯一的按需加载解决方案...

你只需要通过AJAX调用脚本文件本身即可...

<script type="text/javascript">
$(function () {
    $.ajax({
        type: 'get',
        cache: false, 
        url: 'svc.html',
            success: function(response) {
                if (response) { // If any response aka, loaded the svc.html file...
                    $('#main').html(response);
                    $.ajax({
                        url: 'plugin.js',
                        dataType: 'script',
                        success: function(reponse) {
                            // You can now do something with the response here even...
                        }
                    });
                }
            }
    });
});
</script>

我的方式

在显示任何内容之前,制作某种类型的加载器而不是实际页面上的加载。 (网络上有很多使用jQuery的教程可供参考。)


我认为你不能无需任何“努力”地一遍又一遍地执行Javascript的原因是,这样做太容易破坏它,并使其重复执行该任务一百万次。(这只是一个理论。)而且你的服务器也不会喜欢这样做。 - Daniel H. Hemmingsen
嘿,丹尼尔,感谢你的加入!预加载文件是不合适的,因为根据用户想要做什么,我们会加载不同的文件。因此,加载所有内容将是昂贵且没有太大用处的。 - ekkis
不客气。已经折腾了半个小时,甚至一个小时了。应该睡觉了。^^ 嘿嘿。(有时候作为程序员的诅咒。) - Daniel H. Hemmingsen
哎呀,我想发布更多的内容。加载JS文件并不重要,现在最重要的是你是否真正使用它。(以Coffee And Power为例,我认为我们大约有50-100个JS文件。)只有在需要时才能“激活/启用”实际代码。(我们真正使用AJAX的唯一目的就是将数据发送到PHP。粗略地说。) - Daniel H. Hemmingsen

0
这里的问题之一是您无法在每个页面重新加载jQuery,只有主页面需要调用jQuery:
<script type='text/javascript' src='jQuery-url'></script>

或者只有一个页面可以调用它,并且只有子页面(是的,从那里加载的页面)才能使用jQuery。

在我学习教训之前,我遇到过这种情况几次。这可能不是解决您问题的全部答案,但可能是解决问题的开端。


我只在子页面中加载它,因为可以直接调用它并查看没有jQuery .load()的情况下一切是否正常。但这并不是必要的,我已经从arix.com的版本中删除了它 - 但问题仍未解决。 - ekkis

0

好的,经过多次努力,我已经将Kevin B的解决方案实现为一个对他人有用的插件。欢迎改进!(也许我应该把它放在Github上?)

- loadWithJs.js -

// inspired by Kevin B's suggestions
// at: https://dev59.com/rV3Va4cB1Zd3GeqPBYcM

(function ($) {
    $.fn.loadWithJs = function (o) {
        var target = this;
        var success = o.success;

        o.success = function (html) {
            // convert script tags
            html = $('<div />').append(
                html.replace(/<(\/)?script/ig, "<$1codex")
            );
            // detach script divs from html
            var scripts = html.find("codex").detach();
            // populate target
            target.html(html.children());

            // loop through scripts and append them to the dom
            scripts.each(function () {
                var script = $(this)[0];
                var scriptNode = document.createElement('script');

                for (var i = 0; i < script.attributes.length; i++) {
                    var attr = script.attributes[i];
                    $(scriptNode).attr(attr.name, attr.value);
                }
                scriptNode.appendChild(
                    document.createTextNode($(script).html())
                );
                target[0].appendChild(scriptNode);
            });

            if (success) success();
        };

        $.ajax(o);
    };
})(jQuery);

然后调用者可以执行:

$(function() {
    $('#main').loadWithJs({
         type: 'get', cache: false, 
         url: '/svc.html',
         success: function() { console.log('loaded'); }
    });
});

咕噜... 结果发现这个解决方案也有问题: http://stackoverflow.com/questions/8283360/where-are-scripts-loaded-after-an-ajax-call-redux - ekkis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接