代码之家  ›  专栏  ›  技术社区  ›  Andy

执行Ajax响应中返回的JS文件

  •  15
  • Andy  · 技术社区  · 7 年前

    我有一个使用jquery 3.2.1的HTML5应用程序。

    在应用程序的一部分——搜索特性——中,我提出了一个Ajax请求。来自Ajax请求的响应是HTML,这包括一个链接到JS文件的标记,JS文件与应用程序托管在同一服务器上。

    因此,Ajax代码看起来是这样的-用于发出Ajax请求并使用id将响应写入DIV

    $.ajax({
    网址:$('searchRegulations').attr(“action”),
    键入:'post',
    缓存:假,
    数据:$('searchRegulations').serialize()
    })完成(函数(响应、状态、xhr){
    如果(响应){
    $('main.content').hide();
    $('ajaxContent').html(response.show();
    返回false;
    }
    }
    (});
    

    如果我检查ajaxContentI can see that the<script>tag is included in the Ajax response::

    我还检查了我的网络选项卡以确保正确加载了/js/search eu regulations.js,它给出了200响应:

    search_regulations.js中,有一些jquery用于切换存在于ajaxContent中的某些过滤器。

    问题是,此代码似乎只在 时间。当它工作时,它将切换某些筛选器的状态 通过添加/删除类的按钮.activeto elements inside 。browse-ctp_uuu filters-data然后将其写入隐藏表单 使用idtmpfilters

    为了确保脚本是“正在触发”的,我在行中输入了console.log(“search_regulations.js firing”);and sure enough this is shown in the consoleevery time无论脚本是否起作用。

    更奇怪的是,如果在Ajax响应写入页面后将代码剪切/粘贴到控制台中,它将始终起作用。Always worksas expected.

    这是否与脚本进入页面的方式有关?

    我已将脚本粘贴到下面,但我认为这不是其中代码的问题,而是处理浏览器/Ajax响应的方式:

    $(function()。{
    
    console.log(“search_regulations.js firing”);
    
    /*切换按筛选器浏览时的活动(应用)状态*/
    /*@链接https://stackoverflow.com/questions/48662677/switch-active-class-between-groups-of-include-exclude-buttons*/
    $(document).on('click','.browse-ctp_uuu filters-data.include,.exclude',function()。{
    
    var$this=美元(this);
    
    //将名称拆分为数组(例如“find_”=[“find”,“355”])
    var arr=$this.attr('name').split(''u');
    
    
    //切换活动类
    $this.toggleClass(“活动”);
    如果($this.siblings().hasClass(“active”)){
    $this.siblings().removeClass(“活动”)。
    }
    
    //从隐藏窗体中移除筛选器的任何现有实例
    $('tmpfilters input[value=“exclude_'+arr[1]+'”]').remove();
    $('tmpfilters input[value=“find_'+arr[1]+'”]').remove();
    
    //如果应用了筛选器,则将其添加到隐藏窗体
    if($this.hasClass('active')){
    $('tmpfilters').append('<input type=“hidden”name=“tmpfilter[]”value=“'+$this.attr('name')+'“>');
    }
    (});
    (});
    

    提供的奖金说明:

    我提供了一笔赏金,因为这不是一个需要解决的小问题——这一事实证明,没有人给出可行的答案。我希望得到正确的答案:

    1. 可通过jsiddle或等效工具证明。
    2. 解释它是如何工作的/为什么工作的。
    3. 了解Ajax响应是HTML和JS。JS作用于响应中的HTML元素。因此,响应中需要包含HTML和JS,而不是说“将JS添加到全局文件中”(我不希望JS是全局的,因为它特定于给定的HTML响应,并且在应用程序的不同部分可能有所不同)。
    4. 不应使用超时(setTimeout或随便什么)。如果用户与HTML响应中返回的ui元素(如buttons)进行交互,则在超时之前这只会导致我们现在的问题。因此,就我而言,这不是一个有效的解决方案。
    5. 如果在HTML5/jQuery中无法解决此问题,请解释原因并建议任何其他方法来处理它。

    显示通过Ajax返回的HTML和脚本标记的jsFiddle:。

    有几个人要求做小提琴或演示。没有两个人会得到同样的结果——这是一个非常关键的问题——所以我最初没有做过。写入到ajaxContent的HTML返回如下所示:http://jsfiddle.net/v4t9j32g/1/--this is what dev tools in the browser shows following the ajax response.请注意,返回的内容的长度可能会有所不同,因为它是对关键字搜索工具的响应,它会返回一堆筛选按钮。还要注意,此HTML响应包含行<script src=“/js/search_regulations.js”></script>that is where the problematic js is located.and the full contents of that are shown above in this question-the bit that includesconsole.log('search_regulations.js firing').<script>标记链接到与应用程序位于同一服务器上的JS文件。

    所以Ajax代码看起来是这样的——用于发出Ajax请求并用id将响应写入DIV#ajaxContent:

    $.ajax({
        url: $('#searchRegulations').attr('action'),
        type: 'post',
        cache: false,
        data: $('#searchRegulations').serialize()
     }).done(function (response, status, xhr) {
            if (response) {
                $('main .content').hide();
                $('#ajaxContent').html(response).show();
                return false;
            }
        }
    });
    

    如果我检查#AjaxContent公司我可以看到<脚本>标记包含在Ajax响应中:

    enter image description here

    我还检查了我的网络选项卡以确保/js/search_regulations.js正确加载,并给出200响应:

    里面search_regulations.js有一些jquery用于切换存在于#AjaxContent公司.

    问题是,此代码似乎只在 时间。当它工作时,它将切换某些筛选器的状态 通过添加/删除类的按钮.active到内部元素 .browse-ctp__filters-data然后把它们写进一个隐藏的窗体 带身份证#tmpFilters.

    为了确保剧本“开火”,我排了队。console.log('search_regulations.js firing');当然,控制台上也显示了这一点。每一次不管脚本是否起作用。

    奇怪的是,如果在Ajax响应写入页面后将代码剪切/粘贴到控制台中,那么始终有效如预期。

    这是否与脚本进入页面的方式有关?

    我已将脚本粘贴到下面,但我认为这不是其中代码的问题,而是处理浏览器/Ajax响应的方式:

    $(function() {  
    
    console.log('search_regulations.js firing');
    
    /* toggle the active (applied) state on browse by filters */
    /* @link https://stackoverflow.com/questions/48662677/switch-active-class-between-groups-of-include-exclude-buttons */
    $(document).on('click', '.browse-ctp__filters-data .include, .exclude', function(){
    
        var $this = $(this);
    
        // Split name into array (e.g. "find_355" == ["find", "355"]) 
        var arr = $this.attr('name').split('_');
    
    
        // Toggle active class
        $this.toggleClass("active");
        if ($this.siblings().hasClass("active")) {
          $this.siblings().removeClass("active")
        }
    
        // Remove any existing instances of the filter from hidden form
        $('#tmpFilters input[value="exclude_'+arr[1]+'"]').remove();
        $('#tmpFilters input[value="find_'+arr[1]+'"]').remove();
    
        // If a filter has been applied then add it to hidden form
        if ($this.hasClass('active')) {
            $('#tmpFilters').append('<input type="hidden" name="tmpFilter[]" value="'+$this.attr('name')+'">');
        }
    });    
    }); 
    

    提供的奖金说明:

    我提供了一笔赏金,因为这不是一个需要解决的小问题——这一事实证明,没有人给出可行的答案。我希望正确的答案是:

    1. 可以用jsiddle或等效工具证明。
    2. 解释它是如何工作的/为什么工作的。
    3. 了解Ajax响应是HTML和JS。JS作用于响应中的HTML元素。因此二者都HTML和JS需要包含在响应中,而不是说“只需将JS添加到全局文件中”(我不希望JS是全局的,因为它特定于给定的HTML响应,并且在应用程序的不同部分可能有所不同)。
    4. 不应使用超时(setTimeout或者别的什么)。如果用户与HTML响应中返回的UI元素(如按钮)交互之前超时,因此JS被激发…这只会导致我们现在的问题。就我而言,这不是一个有效的解决方案。
    5. 如果这个问题在HTML5/jQuery中无法解决,请解释原因,并建议其他处理方法。

    显示通过Ajax返回的HTML和脚本标记的jsFiddle:

    有几个人要求做小提琴或演示。没有两个人会得到同样的结果——这是一个非常关键的问题——所以我最初没有做过。返回的HTML将被写入#AjaxContent公司如下所示:http://jsfiddle.net/v4t9j32g/1/-这就是浏览器中的dev工具在Ajax响应之后显示的内容。请注意,返回的内容的长度可能会有所不同,因为它是对关键字搜索工具的响应,它会返回一堆筛选按钮。还要注意,这个HTML响应包含<script src="/js/search_regulations.js"></script>.这就是问题JS所在的位置在这个问题中,上面显示的全部内容包括console.log('search_regulations.js firing')

    8 回复  |  直到 7 年前
        1
  •  4
  •   bastos.sergio    7 年前

    我看到的一个问题是你将 onclick 相同的元素 多次…

    由于JS可以通过Ajax请求进行多次加载,因此在再次附加事件之前,首先分离是很重要的。

    另一个问题是,您正在运行代码 $(document).ready 事件(即加载HTML文档和准备好DOM时),但是您最好在 $(window).load 事件(在完全加载整个页面时,执行稍晚一点,包括所有帧、对象和图像)

    例子 :

    $(window).load(function() {  
    
        console.log('search_regulations.js firing');
    
        //off: detach the events
        //on: attach the events
        $('.browse-ctp__filters-data .include, .exclude').off('click').on('click', function(){
            ...
        }
    }); 
    
        2
  •  1
  •   Or Duan    7 年前

    除了每个人在评论中所说的以外,我还想把它写进一个答案。

    事件驱动

    JS是事件驱动的,这并不意味着您必须动态加载脚本,使它们在触发事件时执行函数。更好的方法是在页面加载时加载所需的所有内容,并添加事件侦听器,这将带来更好的用户体验(更少的HTTP请求)和更好的代码可匹配性。

    DR

    一般来说,我会在HTML文件中这样做:

    <script src="/js/main.js">  
    <script src="/js/search_regulations.js" async>  <!-- bonus tip! google "async script tag" -->
    

    这将加载您需要的所有JS。

    记住 search_regulations.js 应该使用jquery添加所需的事件侦听器 on 方法,但由于脚本添加事件侦听器时HTML不存在,因此您可能需要检查 this .

    为什么这样更好?

    • 现在,在一个文件加载另一个文件时,情况非常简单。将来您可能希望添加更多功能和更复杂的代码。当您有相互加载的脚本链时,调试会很痛苦。
    • 用户必须等到 搜索规则.js 要加载的文件,如果它们在移动网络上/文件的大小会变大,这可能是一个糟糕的用户体验。
    • 您仍然可以在应用程序上使用代码 搜索规则.js
        3
  •  1
  •   Sally CJ    7 年前

    注:

    • @ bastos.sergios 已经回答了你的问题-看他的第一点。
    • 信用证也应该给予@ yts 为了“强化”巴斯托斯的答案。

    所以下面应该解决您的 search_regulations.js 脚本:

    $(document)
      // Detach the event handler to prevent multiple/repeat triggers on the target elements.
      .off('click.toggleActive', '.browse-ctp__filters-data .include, .exclude')
      // Attach/re-attach the handler.
      .on('click.toggleActive', '.browse-ctp__filters-data .include, .exclude', function(){
        ... put your code here ...
      });
    

    但是我的 初级的 写这个答案的目的是让你 see the problem with your script .

    如果你好奇的话,下面是我在演示中使用的脚本,稍微修改了一下:

    https://codepen.io/anon/pen/RBjPjw.js

        4
  •  1
  •   Diego Perini    7 年前

    关于要求

    该问题的解决方案可以使用html5/jquery,也可以使用以前的HTML版本和本机JavaScript的组合。使用jquery这样的库的唯一原因是将旧的浏览器带到相同的支持级别,或者修复bug,并在开发人员组之间共享一个标准框架。

    另外,在我看来,无论您将解决问题所需的几行代码放在哪里,它们都可以放在页面的头部分、页面的底部,或者放在Ajax响应有效负载内的boundy需求,而Ajax响应有效负载是经常重新加载的。

    关于这个问题

    真正的诀窍在于你用来实现点击事件触发的方法,你在重复的Ajax响应中的脚本中选择这样做是你的cade在某个时刻失败的原因。

    每次发生Ajax请求时,响应中的脚本都会添加新的事件侦听器。在浏览器因某种原因(内存不足、事件堆栈耗尽或单击事件争用条件)而失败之前,这些问题将不断得到总结。

    关于解决方案

    因此,解决问题的方法是避免在每次发生Ajax请求/响应时添加事件。这是一项可以通过简单的“捕获阶段事件委托”轻松解决的任务(不幸的是,不经常使用)。

    事件监听器只添加一次在顶级节点上,这些顶级节点可以是“document”节点本身或“html”元素或“body”元素,因此在初始页面加载后,始终存在于DOM中的一个元素上。

    这个元素将负责捕获当前页面上的所有点击,或者稍后加载(通过Ajax请求),这个元素将在页面的整个生命周期中以这种方式工作,不需要像我看到的一些建议那样设置/销毁事件。好吧,这可能也会起作用,但会在浏览器引擎上产生更多的工作负载,消耗更多的内存,需要更多的代码,总体来说,速度会变慢,而且说起来也不必要。

    代码示例

    我为您设置了两个代码笔示例:

    委托1在头部分有事件处理代码,如果不存在任何限制或其他未知的怪癖,这是我处理此类问题的首选方法。此版本的优点是代码更短/更快,更易于维护。

    Delegation1

    Delegation2在Ajax响应中有事件处理代码,并且应该满足有关HTML/JS持续加载的要求。有一项检查只安装一次侦听器,并避免多个事件竞速和重叠。

    Delegation2

    示例中的javascript代码包含相关的注释,应该足以解释实现的策略,使其尽可能简单,并在其他类似的情况下可重用。

    这些例子是用老的浏览器编写的,新的浏览器会公开更强大的API,比如 element.matches(selector) 对于事件委托非常有用。我故意避免使用它来获得更广泛的支持。

        5
  •  0
  •   Daniel Lorenz    7 年前

    您需要从HTML中去掉<script>标记,然后手动创建脚本标记以添加到DOM。基本上,您可以通过以下方式将HTML转换为JS:

    var $html = $(data.Html)
    

    然后,像这样拉出所有脚本标记:

     var scripts = [];
            $html.each(function(i, item) {
                if (item instanceof HTMLScriptElement) {
                    scripts.push(item);
                }
            });
    

    然后,在将HTML附加到DOM之前,需要删除HTML中的所有<script>标记。

    $html.find("script").remove();
    
    $("placeToAddHtml").append($html);
    

    一旦添加了去掉标记的HTML,就可以在末尾手动将每个脚本添加到DOM中:

               for (var i = 0; i < scripts.length; i++) {
                    var script = document.createElement('script');
    
                    $(scripts[i]).each(function() {
                        $.each(this.attributes, function(j, attrib) {
                            var name = attrib.name;
                            var value = attrib.value;
                            script[name] = value;
                        });
                    });
    
                    script.text = scripts[i].innerHTML;
                    document.body.appendChild(script);
                }
    

    希望这能达到你的目的!

    编辑:您可能需要在这里构建脚本标记,如果它们相互依赖,则需要同时附加所有拉出的脚本。这样,您只需添加一个内部同时具有所有其他标记的巨型脚本标记。

        6
  •  0
  •   mopsyd    7 年前

    为了避免在不实现平面计时器的情况下出现竞争条件,需要返回一个Ajax响应,该响应将脚本与HTML分开,例如返回两个元素的JSON对象(脚本或脚本数组以支持需要多个元素的情况),以及包含字符串化HTML标记的第二个元素)

    来自Ajax的JSON响应示例:

    {
        "script": "/yourscript.js",
        "html": "<p>Your markup...</p>"
    }
    

    这个(解析的)JSON将被称为 xhrResponse 为了简洁起见。

    在应用到DOM之前,附加 a preload tag 对于您的脚本,在加载dom元素之前,它们将在后台开始解析,而不加载,这将最小化总体加载时间线,并有助于减轻正在进行的争用条件的可能性:

    document.getElementsByTagName('head')[0].appendChild( '<link rel="preload" href="' + xhrResponse.script + '" as="script">' );
    

    这将开始在后台加载脚本,而不执行它,因此可以在稍后将脚本标记应用到DOM时立即应用它。

    然后,您将希望在下一步追加DOM元素,然后在新的DOM内容解析之后应用脚本。您可以在JSON响应中直接传递标记,也可以返回到模板文件的链接,并与上面提到的模板本身的预加载方法一起使用。如果您更关心的是内容上逃避问题的可能性,请执行后者。如果您更关心带宽和延迟,请执行前者。这两者都有一些小的警告,这取决于您的用例。

    document.getElementsByTagName('head')[0].appendChild( xhrResponse.html );
    

    然后,您将要检查应用的HTML内容是否已在DOM中解析。任意等待计时器不是一个很好的方法,因为它很可能使用户等待的时间比需要的时间长,并且如果内容在任意计时器长度之前没有解决,偶尔的网络瓶颈也可能导致脚本偶尔阻塞(如果您从链接中提供模板,则更严重的问题是而不是字符串化的HTML标记),或者如果最终用户当前可用的内存不足(旧机器、bzillion选项卡打开,或者非常大的HTML有效负载,即使是直接字符串化的服务)。

    请看 this answer 对于一个非常健壮的解决方案,检查动态添加的DOM内容是否正确解析。

    一旦解决了这一问题,然后在后面应用脚本元素,如下所示:

    var script = document.createElement('script');
    script.src = xhrResponse.script;
    script.async = true; // use false here if you want synchronous loading for some reason
    document.head.appendChild(script);
    

    前面提到的preload标签应该确保您的脚本已经被浏览器本地化,或者已经很快完成了。然后,您可以检查它是否已使用就绪状态事件侦听器完成,如果它仍然给您带来问题:

    script.onreadystatechange = function() {
        if (script.readyState === "complete") {
            // debug stuff, or call an init method for your js if you really want to be sure it fired after everything else is done.
        }
    }
    

    应该注意,在上述就绪状态侦听器中,如果预加载在逻辑的这一部分之前解决,则就绪状态将不会更改(因为它已经完成),并且这一部分逻辑将不会执行。根据DOM内容的权重,这可能适用于您,也可能不适用于您。


    这种方法将确保以正确的顺序加载,并将脚本和标记分离,这样,无论是现在还是将来,都可以在其他地方独立地循环使用。

        7
  •  -1
  •   Felipe Mora    7 年前

    考虑到如果将此添加到Ajax参数中,所有调用都将正常:

    异步:假

        8
  •  -1
  •   Codex    7 年前

    我认为问题可能是: 1。include和exclude触发器具有相同的函数。处理两者的内部逻辑错误。(因为你告诉我50%的时间都能用)。 2.在附加HTML之后,尝试更新DOM。

       componentHandler.upgradeDom();
    

    刚刚

     $('#ajaxContent').html(response).show();
    

    希望有帮助!!!!