在大型网站上管理document.ready事件

时间:2011-09-28 10:59:17

标签: javascript jquery maintainability document-ready

注意:我现在已经创建了一个jQuery插件,这是我尝试解决此问题的方法。我确信它可以改进,我可能忽略了很多用例,所以如果有人想提供反馈,请随意:-) https://github.com/WickyNilliams/ReadyBinder

我没有这样的问题,但认为这将是一个有趣的讨论点,我希望人们对此有一些有趣的想法。

基本上,我在一个大型网站上工作,我们越来越多地编写越来越多的JavaScript。这很好,我喜欢JS独特的方法,我发现在语言的一些较暗的年代中可爱的奇怪之处;-)然而,一直困扰我的一件事是如何管理文档就绪事件,因为它们变得越来越大随着时间的推移(因此对于所服务的页面不太专注/特定)

问题是我们有一个JS文件(合并和缩小,虽然这对我的问题来说无关紧要)。大多数JS都是使用揭示模块模式编写的,jQuery是我们选择的框架。因此,我们所有的JS功能在逻辑上都分为方法,命名空间,然后在脚本文件的底部,我们有这个

$(function(){
    //lots of code here, usually calling encapsulated methods 
    //on our namespaced revealing module
});

问题在于,并非本文​​档就绪处理程序中的所有代码都与每个页面相关。例如,在一个页面上只有10%可能是相关的,在另一个页面上,80%可能是相关的。对我来说,这感觉非常错误,我觉得我应该只执行每页所需的代码,主要是为了提高效率,还要保持可维护性。

我搜索谷歌搜索此问题的方法,但找不到任何东西,也许我只是在寻找错误的东西!

无论如何,所以我的问题是:

  • 有没有人想过这个问题?
  • 其他人认为这实际上是一个问题吗?
  • 您的代码中是否有一个庞大的,包罗万象的文档就绪处理程序,还是更专注于所服务的页面类型?
  • 如果是后者,您如何管理?多个处理程序在JS中切换或动态吐出文档就绪处理程序服务器端?

期待人们对此事的看法。

干杯

9 个答案:

答案 0 :(得分:9)

这是我在我的rails mvc项目中用大量的javascript做的,我为js中的控制器创建了一个单独的命名空间,类似于rails控制器

class BusinessController
   def new
   end  
   def index
   end
end

Var Business =  {
      init : function(action) {
         //code common to the Business module
         //even add the common jquery doc ready here, its clean
         //and call the page specific action
         this[action]();
      },
      new : function() {
             // check jquery document ready code here, thats relevant to this action
             //New rental page specific code here
      },
      index : function() {
             //  check jquery document ready code here, thats relevant to this action
             //index rental page specific code here 
      }
}

并且在视图代码(服务器端)上只需通过

启动页面特定的js代码
<script type="text/javascript"> 
 <%= controller_name %>.init('<%= controller.action_name %>'); 
//which will render to
//  Business.init(index);
</script>

你可以调整它以使其适用于任何语言。这种方法并不关心您是单个文件还是多个文件。

答案 1 :(得分:2)

我认为这个问题必须直观的解决方案是减少加载时的工作量。

如果您的代码类似于:

$(function () {
    // Binds a single click event (which will never be triggered if the hypothetical
    // widget is never used), regardless of how many widgets are used on the page.
    $(document).delegate('.rating-widget', 'click', namespace.rating.handler);
    // and so on for similar things which simply require event handler registration

    // Initializes each of the forms on the page, letting the initializer take care
    // of any details (datepicker widgets, validation, etc) based on the root element
    $('form.fancy').each(namespace.fancyform.initializer);
    // and so on for similar things that require initialization

    // ... (for each type of thing on the page requiring initial setup,
    //      find the most general/high level pattern that sufficient)
});
事情应该是相对可维护的,即使有几十行。在使用组件代码时,此级别没有复杂/有趣的逻辑来更新/记忆,因为这个级别的所有内容都很简单,所以很容易阅读。在这一点上,没有必要发明一些复杂的注册系统;它不会比.delegate.each更简单。

请注意,以上所有内容都可以优雅地处理不使用该组件的情况。在这些情况下,很少或根本没有工作,也没有条件逻辑。

关于如何实现处理程序/初始化程序的有趣想法,您可能需要查看,例如,Doug Neiner上周在jQuery Boston上发表的“Contextual jQuery in practice”演讲。

答案 2 :(得分:1)

好问题。我实际上通过使用自定义页面加载事件来处理这个 所以在我的核心.js文件中,我有一个如下的类:

var Page = {
    init: function(pageName) {
        switch (pageName)
        {
            case: 'home': {
                // Run home page specific code
            }
            case: 'about': {
                // Run about page specific code
            }
            ...
        }
    }
}

您可以通过特定于页面的$(document).ready()或使用某种URL解析器从核心脚本中调用此方法(实际上,使用URL解析器可以实现任何方式):

$(document).ready(function() {
    // http://www.mydomain.com/about
    var currentPage = window.location.pathname;   // "about"
    Page.init(currentPage);    
});

window.location DOM参考:https://developer.mozilla.org/en/DOM/window.location

答案 3 :(得分:1)

首先,我将特定的课程放在身体或特定的容器上,例如articlesform-validationpassword-meter,....如果我有一个MVC应用程序,我更喜欢将控制器名称放入一个body类。这并没有太大的伤害,也可以用于造型。

然后在每个document.ready块上检查是否存在这样的类,如果它不存在,我将从此函数返回。这种方法更简单,因为它没有if子句中的主代码。这种方法类似于用于检查函数先决条件的断言。

示例:

$(function(){
    if($('body').hasClass('articles') === false){ return; }

    //body of the articles code
});

$(function(){
    if($('body').hasClass('password-meter') === false){ return; }

    //include password meter in page
});

这种方法的好处是,没有不需要的代码可以潜入现成的回调,这使得重用更容易。缺点是你得到很多回调块,如果你不注意重复的代码可以很容易地潜入你的应用程序。

答案 4 :(得分:0)

我知道你来自哪里。对于移动使用的正确的webapps,它可能会让用户下载一个充满无关代码的大量JS文件而感到愚蠢(一个JQuery API是一个糟糕的开始!)。

我所做的,可能是对或错,但似乎确实有用,包括在标签之前逐页包含所需的功能。 (大多数情况下,这个位置绕过document.ready问题)。但是不禁觉得这种做法太简单了。

我有兴趣听到其他答案,所以这是一个很好的问题,+ 1。

答案 5 :(得分:0)

  

“问题是我们有一个JS文件”

只要您为所有页面包含相同(合并)的JS文件,我不确定如何避免它。您可以使用一些服务器端逻辑来合并不同的部分以使用不同的页面,但是您可能会失去JS的客户端缓存的好处,所以......

我曾经参与过的项目(通常)使用了多个JS文件,每个页面都有一对共享,其他包含或不包含在内 - 有些JS文件只包含在一个页面中。这意味着几乎每个页面都包含公共库函数,无论它们是否使用它们,但是常见的JS文件被缓存,因此它并不重要。页面特定的代码确实是特定于页面的。

我无法从你的问题中看出你是否意识到这一点,但是你可以拥有多个文档就绪处理程序(我相信jQuery会按照绑定的顺序执行它们吗?)。因此,如果您将JS拆分起来,每个JS文件都可以包含自己的文档。当然,这种方法有其自身的开销,但我认为这比将所有内容放在同一个文件中更好。

答案 6 :(得分:0)

首先,如果您使用的是jQuery,那么document.getElementsByTagName并不是必需的,您可以使用$(“tagname”)。看看jQuery Selectors documentation更加神奇!

您正在进行的工作解决方案就是我的方法,但您不需要去定义额外的功能,获取bodyClasses,运行循环或任何爵士乐......为什么不直接使用jQuery的.hasClass()函数?

$(function() {
    if($("body").hasClass("article")) {
        alert("some article specific code");
    }
    if($("body").hasClass("landingPage")) {
        alert("some landing specific code");
    }
});

的Bam。一切你需要的。如果您想提高效率,可以重复使用一个正文查询:

$(function() {
    var body = $("body");
    if(body.hasClass("article")) {
        alert("some article specific code");
    }
    if(body.hasClass("landingPage")) {
        alert("some landing specific code");
    }
});

这是jsfiddle:http://jsfiddle.net/W8nx8/6/

答案 7 :(得分:0)

拥有较少的javascript文件可最大限度地减少发送到服务器的请求数量,并缓存文件使第二页和第三页加载速度更快。 通常,我想在以下页面(而不仅仅是当前页面)中需要js文件的某些部分的可能性。这使我将我的js代码分为两种类型: 代码进入我的“main.js”,即使没有使用,也会被客户端下载(但不一定要执行,我会解释),第二类代码分别放在不同的文件中。您实际上可以创建按概率分组的多个文件。这取决于项目的详细信息。如果它是一个大型项目,对用户行为进行一些统计分析可能会揭示有趣的方法来分组js代码。 现在代码已下载到客户端,我们不想执行所有代码。 你应该做的是有不同的布局或页面。在布局或页面中放置一个部分。在局部集内设置一个javascript变量。检索javascript代码中的javascript变量,并确定是否要执行某些代码。

即:

创建部分“_stats.html.erb”,将此代码放入其中:

<script type="text/javascript">
    var collect_statistics = <%= collect_statistics %>;
</script>

将部分放在您要收集统计信息的页面中: show.html.erb

<%= render "_stats", :collect_statistics => false %>

现在在您的js文件中,执行以下操作:

$(document).load( function() {
 if (typeof collect_statistics != "undefined" && collect_statistics) {
  // place code here
 }
});

我希望这有帮助!

答案 8 :(得分:0)

我喜欢RameshVel方法。我还发现了Paloma gem来创建特定于页面的JavaScript。例如:

Paloma.controller('Admin/Users', {
  new: function(){
    // Handle new admin user
  }
});