【问题标题】:managing document.ready event(s) on a large-scale website在大型网站上管理 document.ready 事件
【发布时间】:2011-11-26 18:51:45
【问题描述】:

注意:我现在创建了一个 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 中切换或在服务器端动态吐出文档就绪处理程序?

期待大家对此事的看法。

干杯

【问题讨论】:

  • 这并不完全相关,因为我不关心脚本加载,而是如何限制在准备好文档时执行的代码量(特别是如果你的所有 JS 都合并到一个文件中,虽然单独的文件也将是有益的)
  • 坦率地说,您甚至不应该加载您不会使用的代码。我建议查看require.jshead.js
  • 预取是一种非常有效的方法。预先加载所有(或至少大部分)代码可以节省后续请求的未来带宽使用。然而,requireJS 正在解决一个不同的问题——脚本加载。您仍然需要一种管理文档就绪处理程序的方法。事实上 requireJS 会与我的建议一起工作,而不是与我的建议竞争。您可以让相关文档就绪代码(我正在尝试解决的问题)动态加载他们需要的脚本,然后再将脚本用于任何目的(requireJS 试图解决的问题)

标签: javascript jquery maintainability document-ready


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    这是我在 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>
    

    您几乎可以对其进行调整,使其适用于任何语言。而且这种方法不关心你是单个文件还是多个文件。

    【讨论】:

    • 非常简洁的解决方案!我喜欢使用普通的 ol' JS 以及它如何与您的控制器相结合。这会通过在页面上内联吐出文档就绪的内容而丢失一些客户端缓存,但它只有一行长可以忽略不计!我并不太热衷于吐出 JS 服务器端,但考虑到它非常通用,这是一种公平的方法。我几乎完成了一个 jQuery 插件来连接。如果您想查看它,我会在完成后将其发布在这里 - 现在只需要一个时髦的名字!感谢您的思考:)
    • @mr.nicksta,我很高兴你喜欢它...我已经遵循这种方法很长一段时间了...它干净且非常易于管理...
    • 很高兴听到它也经过了尝试和测试。你的例子有错误吗? &lt;%= controller_name %&gt;.init&lt;%= controller.action_name %&gt; 会输出Business.initindex,对吗?括号不见了:)
    • 是的,应该是 .init('');
    • 这是我最喜欢的答案。在清洁度、可维护性和简单性方面,它完全符合我的要求。尽管我实际上无法使用这种方法,因为我目前被 WebForms 应用程序困住了,但我会奖励你出色的、深思熟虑的答案。享受你的 100 分 :-)
    【解决方案3】:

    我认为这个问题必须直观的解决方案是,简单地说,减少加载时执行的工作量。

    如果您的代码如下所示:

    $(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”演讲。

    【讨论】:

    • 这几乎是我目前希望摆脱的方法。如果页面上没有这样的元素,它可以正常工作,但是在大型站点上,您的文档就绪处理程序会很快变大,因为您的所有逻辑都针对每个页面进行。还有$('form.fancy').each(namespace.fancyform.initializer);,尽管优雅地处理了不存在此类元素的情况,但仍然在浪费处理能力(在可能是严重不足的客户端上)搜索DOM,即使它证明搜索没有结果。谢谢你的谈话链接,稍后会看:)
    【解决方案4】:

    首先,我将特定的类放在主体或特定容器上,例如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
    });
    

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

    【讨论】:

    • 你在问题​​底部查看了我的 github 吗?它将以一种更简洁的方式解决您的情况:) 您只需要一个准备好文档的处理程序,并且不需要 if 语句!如果你检查出来,让我知道你的想法。此外,您上面的代码可以简化,下面的 if 语句等同于您上面的代码:if(!$('body').hasClass('password-meter'))。希望对您有所帮助
    • 忽略了这个链接,它会简化我的布局——所以我必须想出一个更好的答案 8^)。至于 if 语句的简化:我喜欢这种方式,因为感叹号很容易被忽略,三重比较只会让你眼前一亮。
    • 好点,代码可读性应该始终是最重要的。您当前的方法的另一个低效率(我知道这次是一个有效的观察;-))也必须一直重新选择身体。随意试用我的代码,看看你是如何找到它的,如果你有任何建议等。我可能会将赏金奖励给对改进我的方法有帮助的人!
    【解决方案5】:

    使用较少的 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
     }
    });
    

    我希望这会有所帮助!

    【讨论】:

    • 感谢您的回答。虽然这种方法确实允许根据某些情况执行代码,但我试图避免编写大量您的方法要求的 if 语句
    • 如何创建一个为您完成布线的地图,以及一个为您完成 js 代码的代码生成器。我在 js 中而不是在呈现 js 的 erb 文件中这样做的原因是因为我需要缩小我的文件并静态地提供它们。 JS 代码生成器解决了这个问题,同时让您的代码管理更加轻松。
    • 我认为在服务器端自动生成 Javascript 很麻烦,因为您的后端突然需要知道(并且能够生成)javascript - 这会破坏关注点分离。你看到我的github了吗?我编写了一个 jQuery 插件,它自动连接需要执行的代码,类似于您所做的,但它使文档准备就绪更加简单。
    【解决方案6】:

    很好的问题。我实际上通过使用自定义页面加载事件来处理这个问题。 所以在我的核心 .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.locationDOM 引用:https://developer.mozilla.org/en/DOM/window.location

    【讨论】:

    • 感谢您,这与我的想法非常相似,只是我正在考虑使用 CSS 类来识别要运行的代码,并且您使用 URL 作为指示符。但是,我有这么多页面(成千上万),通过 URL 进行分类是不切实际的。非常感谢,这是我想看到的,很多想法,一些讨论等:-)
    【解决方案7】:

    首先,如果您使用 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");
        }
    });
    

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

    $(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/

    【讨论】:

    • 我完全理解选择器,不使用它们是有意识的决定,不用担心 :-) 另外,我想循环遍历这些类,这样我就不必添加每当我想在准备好的文档上附加新功能时,一个新的if 语句。您的方法太清楚body元素上可能出现的类,检查特定条件。我的是一种更通用的方法,因为它不关心主体附加了哪些类,它只关心我的对象字面量上是否存在匹配方法。
    • 做出“有意识的决定”来避免使用 jQuery 不一定是个好主意。您否认自己拥有 jQuery 团队工程师进入库的所有优化(其中许多是特定于平台的)的优势。你可能弊大于利。
    【解决方案8】:

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

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

    我从事的项目(通常)使用了多个 JS 文件,每个页面都有一对,其他的是否包含在内或不合适 - 一些 JS 文件仅包含在一个页面中。这意味着几乎每个页面都包含公共库函数,无论它们是否使用它们,但是公共 JS 文件会被缓存,所以这并不重要。特定于页面的代码确实是特定于页面的。

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

    【讨论】:

    • 从页面加载的角度来看,预先下载所有 JS 并缓存客户端绝对是有意义的,所以我认为合并文件很好。但是如果你在文档就绪处理程序中做很多事情,那么有很多代码 execute 是没有意义的(下载是一次性的,但执行不必要的代码会产生成本每次页面加载)。我知道有多个准备好的处理程序,但我认为它并不能真正解决问题,并且可能会引发它自己的性能问题(可能触发许多事件比触发一个更大的事件成本更高)
    • 是的,我只建议在多个JS文件的场景下使用多个ready handler。这样浏览器仍然在缓存各种 JS 文件,但是任何给定的页面只加载它需要的那些并且只执行它需要的代码。我假设触发许多只执行您需要的事件比触发一个更大的事件(根据您的问题)可能 90% 不相关要好。
    • 您说得有道理,但页面加载的主要因素是 HTTP 请求的数量,我希望遵守此规则。我已经开始考虑一种方法,我可以以声明方式将类添加到正文(服务器端)。这些将描述页面的类型(以保持语义),然后我可以使用 JS 查找这些,并将它们用作访问某个对象上特定功能的键。这种额外的逻辑和查找会产生一些成本,但应该通过关注每页执行的代码量来提供收益。我需要进行基准测试。一个粗略的原型:jsfiddle.net/v5QZn
    • 对不起,原来小提琴中的小错误,虽然它仍然有效。这个在语法上是正确的:jsfiddle.net/v5QZn/1
    • 嗯,这是一个有趣的问题,因此我之前给了你赞成票。我快速浏览了您的原型,并且(尽管我相信您会随时对其进行调整和美化)这是满足您的一个 JS 文件要求的更好方法,而不是一系列 if /否则如果。不要忘记即使没有执行它仍然必须被解析。 (至于我,我想我会坚持一个通用加几个页面特定的JS文件的方法。)
    【解决方案9】:

    我知道你来自哪里。正确的移动网络应用程序让用户下载一个充满无关代码的大量 JS 文件会让人觉得很愚蠢(JQuery API 是一个糟糕的开始!)。

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

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

    【讨论】:

    • 我的问题是,如果您只包含每页内联的脚本,您将无法获得客户端缓存的好处。我希望预先下载所有脚本,但只在每个页面上执行必要的代码。我一直在考虑某种命名空间文档就绪处理程序,它根据某些标志(例如添加到正文标记的类)切换执行的代码。这似乎可行,但我无法理解如何使用一些 JS 魔法来实现。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-05
    • 2012-03-01
    • 1970-01-01
    • 2014-02-19
    相关资源
    最近更新 更多