【问题标题】:JS for different dynamically loaded content in a fully ajaxed website用于完全 ajaxed 网站中不同动态加载内容的 JS
【发布时间】:2015-04-14 18:33:27
【问题描述】:

这是一篇完全更新的帖子,旨在通过改进的代码概念更好地解释问题(基于到目前为止此处给出的答案)

我尝试实现一个完全 ajaxed 的网站。但是我遇到了多个绑定事件的问题。

这是基本的 HTML:

<header id="navigation">
    <ul>
        <li class="red" data-type="cars">Get Cars</li>
        <li class="red" data-type="vegetables">Get Vegetables</li>
    </ul>
</header>
<div id="anything">
    <section id="dymanic-content"></section>
</div>

导航是动态创建的(#navigation 的内容可以替换为另一个导航),因此导航元素的绑定如下所示:

$('#navigation').off('click', '.red').on('click', '.red', function() { 
    var type = $(this).attr('data-type');
    var data = { 'param': 'content', 'type': type };
    ajaxSend(data);
});

网站的内容也在动态加载。比如有两种不同的内容:

1:

<div id="vegetables">Here are some informations about vegetables: <button>Anything</button></div>

2:

<div id="cars"><img src="car.jpg"></div>

在加载内容的同时,我还将加载一个特定的 JS 文件,该文件包含此类内容所需的所有绑定。所以加载脚本看起来像这样:

var ajaxSend = function(data) {
    $.ajax({ url: "script.php", type: "POST", data: data, dataType: "json" })
    .done(function( json ) {
        if (json.logged === false) { login(ajaxSend, data); }
        else {
            $.getScript( 'js/' + json.file + '.js' )
            .done(function( script, textStatus ) { 
                $('#result').html(json.antwort);
            });
        }
    });
}

当您传递所需结果类型(即蔬菜或汽车)的参数时,结果将显示在#result 中。还会加载文件cars.js 或vegets.js。

所以我的问题是避免多个事件绑定。我就是这样做的:

cars.js:

$('#result').off('mouseover', 'img').on('mouseover', 'img', function () { 
    // do anything
});

vegetables.js:

$('#result').off('click', 'button').on('click', 'button', function () { 
    // do anything
});

这是正确的方法吗?我认为使用off() 只是一种解决方法。因此,如果有任何改进,我将不胜感激!

此外,如果用户多次单击导航,我不知道是否有问题:在这种情况下,js文件会被多次加载,不是吗?那么这个概念是否有多个绑定?

【问题讨论】:

  • 您可以拥有每个动态内容的特定JS文件,该文件将加载动态内容。
  • 你能给出一个代码示例吗?我想用不同的 JS 文件来做到这一点。但问题是一样的,不是吗?我必须使用委托事件,对吗?加载新内容时是否必须卸载 JS 文件?
  • 对原始代码进行了所有更改,这应该是一个新问题......
  • 同一个话题,同一个问题。我刚刚更新它以使其更清晰。我不认为每件事都应该总是有一个新问题......

标签: javascript jquery ajax events javascript-events


【解决方案1】:

当您提到一个一个完全 ajax 的网站时,我认为是一个 SPA -- S单个 P年龄一个应用程序。

区别可能是语义,但 AJAX 意味着 DOM 操作,而 SPA 意味着 模板导航

加载页面时会加载 HTML 模板。每个模板映射到特定的导航路线。主要的变化不是事件映射,而是显示的模板,以及是否加载了新数据。

See my example AngularJS SPA Plunkr

AngularJS 路由看起来像这样:

   scotchApp.config(function($routeProvider) {
     $routeProvider

     // route for the home page
     .when('/', {
       templateUrl: 'pages/home.html',
       controller: 'mainController'
     })

     // route for the cars page
     .when('/cars', {
       templateUrl: 'pages/Cars.html',
       controller: 'CarsController'
     })

     // route for the vegetables page
     .when('/vegetables', {
       templateUrl: 'pages/Vegetables.html',
       controller: 'VegetablesController'
     });
   });

所以每个路由都有对应的 HTML 模板和控制器(其中定义了回调函数)。

出于 CDN 目的,模板可以作为 JSON 传回

     // route for the vegetables page
     .when('/vegetables', {
       template: '<div class="jumbotron text-center"><div class="row"><h3>Cars Page</h3>Available Cars: <a class="btn btn-primary" ng-click='LoadCars()'>LoadCars</a></div><div class="col-sm-4"><a class="btn btn-default" ng-click="sort='name'"> Make/Model</a></div><div class="col-sm-2"><a class="btn btn-default" ng-click="sort='year'"> Year</a></div><div class="col-sm-2"><a class="btn btn-default" ng-click="sort='price'"> Price</a></div><div class="row" ng-repeat="car in cars  | orderBy:sort"><div class="row"></div><div class="col-sm-4">{{ car.name }}</div><div class="col-sm-2">{{ car.year }}</div><div class="col-sm-2">${{ car.price }}</div></div></div>',
       controller: 'VegetablesController'
     });
  • 在“模板化”应用程序中,每种类型的 HTML 都会加载一次。

  • 事件和控件绑定一次。

  • 增量更改是 JSON 来回传递。您的端点不负责呈现 HTML。他们可以很平静,并且有明确的关注点。

  • 您可以使用 AngularJSEmberBackboneJQuery 等。

【讨论】:

  • 这看起来很有趣。但这对我来说是全新的。模板必须是 HTML 文件?到目前为止,我一直使用由 php 文件生成的 HTML 代码(取决于给定的参数)。这些 HTML 代码已通过 AJAX 传输到主页。因此,对于 angular.js,您会建议使用静态 html,对吗?
  • @user3142695,实际上,模板可以创建为 JSON。而不是通过template: &lt;div&gt;&lt;/div&gt;...。我在答案中添加了一个示例。但是,是的,这些不是动态 HTML。他们的行为是动态的。 AngularJS 或 Ember 等框架具有强大的功能,可以轻松地使您的页面动态化。看看我的样本。我创建了报告,能够对报告进行排序,并且几乎不需要任何代码就可以加载更多数据。而不是加载动态部分(我以前也用 ASP.NET 来做),更多的逻辑进入您的客户端页面。这是一种新的思维方式,但 IMO 的设计要好得多。
  • 这个想法是您的 html 页面是静态的,但在行为上是动态的。因此它们符合数据或用户交互。所以你的端点只关心数据,页面与用户请求交互。我承认,这是一种截然不同的方法。这是看待应用程序的一种截然不同的方式。
  • @user3142695,请记住,您可以混合使用 JQuery 和 AngularJS。我已将 Angular 添加到现有项目站点。纯 Angular 的结果最好,但过渡不是必需的。我怀疑一旦你开始使用 AngularJS,你就会越来越喜欢它。但是你不需要放弃所有当前的工作来使用 Angular 的模板和路由。
  • 这正是我最大的问题。我的 Query-Project 工作得很好,我做了很多工作。现在我有一种感觉,1)我必须学习 angular.js 新的 2)我必须创建我的项目全新
【解决方案2】:

cars.js:

    $('#result').off('mouseover', 'img').on('mouseover', 'img', function () { 
        // do anything
    });

vegetabels.js:

    $('#result').off('click', 'button').on('click', 'button', function () { 
        // do anything
    });

我不确定,但是,如果用户首先单击导航上的汽车类型,那么 ('mouseover', 'img') 侦听器会注销然后再次注册,对吗? 然后当用户点击导航上的蔬菜类型('click', 'button') - 取消注册(但!!!'mouseover', 'img' - 保留!!!),如果然后用户点击脚本没有('mouseover', 'img') 侦听器但内容有img 的某些类型的导航 -然后发生非法内容监听器(来自先前的操作)。

因此,您需要在开始加载新内容和脚本之前清除所有注册到#result 的侦听器,也许:

    var ajaxSend = function(data) {
        $.ajax({ url: "script.php", type: "POST", data: data, dataType: "json" })
        .done(function( json ) {
            if (json.logged === false) { login(ajaxSend, data); }
            else {
                $('#result').off();
                $.getScript( 'js/' + json.file + '.js' )
                .done(function( script, textStatus ) { 
                    $('#result').html(json.antwort);
                });
            }
        });
    }

cars.js:

    $('#result').off().on('mouseover', 'img', function () { 
        // do anything
    });

vegetabels.js:

    $('#result').off().on('click', 'button', function () { 
        // do anything
    });

编辑:关于多次加载脚本。我没有找到明确的答案,我认为它取决于浏览器和 jquery 实现,并且每次创建新脚本时,即使它是较早创建的,也可能会创建新脚本,因此可能有两个缺点:

  1. 重复加载相同的脚本表单服务器,如果不是浏览器也不是 jquery 没有缓存它
  2. 通过脚本淹没 DOM anp 浏览器的解释器

但是,取决于 JQuery 文档。

缓存响应

默认情况下,$.getScript() 将缓存设置为false。这会将时间戳查询参数附加到请求 URL,以确保浏览器在每次请求时下载脚本。您可以通过使用 $.ajaxSetup() 全局设置缓存属性来覆盖此功能


$.ajaxSetup({ cache: true });

你可以依赖JQuery cache (there is an option to cache only scripts),或者实现你自己的,例如:

    var scriptCache = [];
    function loadScript(scriptName, cb){
      if (scriptCache[scriptName]) {
        jQuery.globalEval(scriptCache[scriptName]);
        cb();
      } else {
        $.getScript( scriptName )
        .done(function( script, textStatus ) { 
          scriptCache[scriptName] = script;
          //$('#result').html(json.antwort);
          cb();
        });
      }
    }

【讨论】:

  • 那么在cars.js/vegetables.js中就不需要off()了,不是吗?
  • 在每个动态加载的脚本中都需要它,因为您无法预测将按什么顺序加载哪些脚本,或者您只是在 ajaxSend 中添加 $(#result).off() 就像在第一个变体中一样在任何 dyn 脚本中都不需要 .off() ,但这并不可靠(因为监听器未注册但新内容和脚本加载失败的情况)
  • 我不明白。使用$('#result').off(); 会删除所有事件。然后json.file.js 将被加载。现在#result 上没有事件,所以你不必使用off()...为什么我错了?
  • 对不起,我的回答可能会误导我给出了两个变体:1) $("#result").off() - 在 ajaxSend 中调用,在动态加载的脚本中不需要 .off() (cars/vagetables) 2) 在 dyn 脚本中使用 .off() 代替 .off(event, element) 在 ajaxSend 变体中不需要 .off() 1) 对于 dyn 脚本/内容失败是有缺陷的,因为它首先清除了侦听器(但要替换的内容可能会留下并出现故障)
  • 哦……明白了。对不起。
【解决方案3】:

首先,我建议你像其他人建议的那样选择像AngularJS这样的框架。

但是,除此之外,您还可以考虑使用namespaces

cars.js:

$('#result').off('mouseover.cars', 'img').on('mouseover.cars', 'img', function () { 
    // do anything
});

vegetables.js:

$('#result').off('click.vegetables', 'button').on('click.vegetables', 'button', function () { 
    // do anything
});

这将是一个改进(并且解决方法会少一些),因为:

(它会完成工作)而不干扰其他 click event 处理程序 附加到元素上。

-- .on() documentation

【讨论】:

  • 在阅读了许多长的 long 答案后,我很高兴终于看到有人提出了明显的解决方案(简单地为事件命名空间):)
【解决方案4】:

您可以创建一个函数来获取要加载的页面名称,并使用单个函数来加载页面。然后让回调函数加载同名的 javascript 文件(具有常见的init 函数)。喜欢:

function loadPage( pageName ) {
  $('#dymanic-content').load( pageName +'.php', function() {
    $.getScript( pageName +'.js', function() {
      init();
    });
  });
}

或者您可以将回调函数名称传递给函数。

function loadPage( pageName, cb ) { 
 $('#dymanic-content').load( pageName +'.php', function() {
   $.getScript( pageName +'.js', function() {
     cb();
   });
 });
}

您也可以使用 Promise 而不是回调来做到这一点。

【讨论】:

  • 但是在这个 JS 文件中,我还是得使用委托事件,对吧?这意味着它是相同的 JS 代码,对吗?加载新内容(和新的 JS 文件)时是否必须卸载?
  • 你可以使用dynamic-content作为你的父元素来添加你的委托事件。
  • 哦,HTML 将在每次调用 loadPage 时被覆盖。
  • 我喜欢你的想法,但不幸的是我并不完全理解它。您能否告诉我上面显示的代码,即example_1.js 应该是什么样子?我会更容易理解。谢谢。
  • pagename.js 文件上方的代码中,将包含该特定页面的所有委托事件。因此,如果它是菜单页面,它将加载 menu.js,其中包含您在加载文件后调用的函数(上面代码中的 initcb)。此函数应为menu 添加所有委托事件。
【解决方案5】:

如果您采用 Web 的 AJAX 方式,请考虑使用 PJAX。这是一个久经考验的创建 AJAX 网站的库,在 github 上使用。

下面带有 PJAX 的完整示例:

HTML:

一旦脚本加载完成,

data-js 属性将用于运行我们的函数。每个页面都需要不同。

data-url-js 属性包含要加载的 JS 脚本列表。

<div id="content" data-js="vegetablesAndCars" data-urljs="['/js/library1.js','/js/vegetablesAndCars.js']">
    <ul class="navigation">
       <li><a href="to/link/1">Link 1</a></li>
       <li><a href="to/link/2">Link 2</a></li>
    </ul>
    <div id="vegetables">
    </div>
    <div id="cars">
    </div>
</div>

模板:您的所有页面都必须具有#content div 作为容器,并具有上述两个属性。

JS:

App.js - 这个文件需要包含在每个页面中。

/*
 * PJAX Global Defaults
 */
$.pjax.defaults.timeout = 30000;
$.pjax.defaults.container = "#content";

/*
*  Loads JS scripts for each page
*/
function loadScript(scriptName, callback) {
    var body = document.getElementsByTagName('body')[0];    
    $.each(scriptArray,function(key,scripturl){
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = scripturl;
        // fire the loading
        body.appendChild(script);
    });
}

/*
*  Execute JS script for current Page
*/
function executePageScript()
{
    //Check if we have a script to execute
    if(typeof document.getElementById('content').getAttribute('data-js') !== null)
    {
        var functionName = document.getElementById('content').getAttribute('data-js').toString();
        if(typeof window[functionName] === "undefined")
        {
            var jsUrl = document.getElementById('content').getAttribute('data-url-js').toString();
            if(typeof jsUrl !== "undefined")
            {
                jsLoader(JSON.parse(jsUrl));
            }
            else
            {
                console.log('Js Url not set');
            }
        }
        else
        {
            //If script is already loaded, just execute the script
            window[functionName]();
        }            
    }
}


$(function(){
    /*
     * PJAX events
     */
    $(document).on('pjax:success, pjax:end',function(){
        //After Successful loading
        //Execute Javascript
        executePageScript();
    }).on('pjax:beforeSend',function(){
        //Before HTML replace. You might want to show a little spinning loader to your users here.
        console.log('We are loading our stuff through pjax');
    });  
});

vegetableAndCars.js - 这是您页面特定的 js 文件。您所有特定于页面的 js 脚本都必须遵循此模板。

/* 
 * Name: vegetablesAndCars Script
 * Description: Self-executing function on document load.
 */
(window.vegetablesAndCars = function() {
    $('#cars').on('click',function(){
        console.log('Vegetables and cars dont mix');
    });
    $('.navigation a').on('click',function() {
        //Loads our page through pjax, i mean, ajax.
        $.pjax({url:$(this).attr('href')});
    });
})();

更多解释:

  1. window 全局 js 命名空间附加了一个函数名,这样可以在不重新加载脚本的情况下重新执行该函数。如您所见,此函数名称必须是唯一的。

  2. 该函数是自可执行的,因此如果用户在不使用 AJAX 的情况下到达页面(即直接进入页面 URL),它将自行执行。

您可能会问,我有on 我的HTML 元素的所有那些绑定呢?好吧,一旦元素被销毁/替换,与它们的代码绑定将被浏览器垃圾收集。这样您的内存使用量就不会飙升。

上述用于实现基于 ajax 的网站的模式,目前正在我客户的一个地方生产。因此,它已经针对所有场景进行了非常多的测试。

【讨论】:

  • 由于导航是网站的主要导航,因此我使用的是帖子中所示的 HTML 标记。所以 section-element 用于内容,header-element 用于导航。我想避免改变这一点。如果我用#content div 包装它,我可以使用页面的主体元素。但这真的应该这样使用吗?
  • content div 只是一个示例。但是,如果您使用body 重新加载内容,则意味着将替换整个页面。如果您的用户选择在加载过程中切换想法并单击另一个 AJAX 链接,这不是一个好的解决方案。加载初始库后,性能非常快。加载页面需要 1 秒。我在我的网站上加入了一个预加载器,这样用户会感觉应用程序最初是在加载其组件,并且比之后减慢每个操作以逐步加载每个库更宽容。
【解决方案6】:

当你在做$('#navigation').on('some-event', '.red',function(){}); 您将事件绑定到 #navigation 元素(您可以使用 $('#navigation').data('events') 看到这一点),但没有绑定到内部的 .red-元素,这就是为什么当您使用新的 js-logic 加载新元素时,您会变得新旧绑定。

如果在您的情况下这是可能的,只需对所有应该与元素一起删除/重新加载的事件使用像 $('#navigation .red').some-event(function(){}); 这样的直接绑定。

【讨论】:

    【解决方案7】:

    在大多数情况下,您在 Web 开发中可以想象到的所有事情都已经完成。你只需要找到它并让它与你的环境一起工作。您的代码存在许多问题,但还有一些问题更让我困扰 - 为什么没有人提及 angularJSrequireJS?使用此类框架和库有很多好处,其中包括

    • 数以千计的教程遍布各地
    • 关于 SO 的成千上万个问题
    • 他们(大部分)都有很棒的插件,可以随时使用
    • 与您的实现相比,它们可能具有更广泛的功能

    还有使用自己的代码的好处

    • 你是唯一懂它的人。
    • 有什么事吗?

    我的意思是你应该使用其他人已经构建的东西,在 99% 的情况下是完全免费的。

    另外使用像 Angular 这样的框架,你最终会得到更干净和可维护的代码。

    【讨论】:

    • 您对框架的建议是正确的。但大多数情况下他们有更多的功能,然后我需要。示例:所以如果你只需要淡入淡出和滑动效果,我自己在纯 JS 中这样做会更好还是应该使用免费且已经完成的 JQuery?
    • 一个框架的'缺点'是需要在angular.js中完全重写整个项目
    • @user3142695 最好包含另一个专注于动画的库,例如 animate.css。这些是一些不会给你带来任何好处的简约问题。技术进步,几百字节不算什么。当然你可以继续抱怨它如何减少电池寿命或增加国外移动互联网的成本,但话说回来,你节省的钱真的值得吗?
    【解决方案8】:

    使用.off(...).on(...) 方法,您可以保证在新绑定之前清除事件,以防您有多个 .js 文件绑定到同一个事件(即:汽车和蔬菜都有不同逻辑的按钮单击)。

    但是,如果不是这种情况,您可以使用类过滤器来检测哪个元素已经具有事件界限:

    $('#result:not(.click-bound)').addClass('click-bound').on('click', 'button', function() { 
         // your stuff in here
    });
    

    这样,选择器将仅将事件绑定到尚未使用类 click-bound 修饰的元素。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-23
      • 1970-01-01
      • 2018-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-07
      相关资源
      最近更新 更多