组织 jQuery/JavaScript 代码的最佳方式(2013 年)[关闭]

Posted

技术标签:

【中文标题】组织 jQuery/JavaScript 代码的最佳方式(2013 年)[关闭]【英文标题】:Best way to organize jQuery/JavaScript code (2013) [closed] 【发布时间】:2013-05-20 03:27:51 【问题描述】:

问题

这个答案之前已经回答过,但已经过时且不是最新的。我在一个文件中有超过 2000 行代码,我们都知道这是不好的做法,尤其是在我查看代码或添加新功能时。我想更好地组织我的代码,无论是现在还是未来。

我应该提到我正在构建一个工具(不是一个简单的网站),其中包含许多按钮、UI 元素、拖放、动作侦听器/处理程序以及多个侦听器可能使用相同功能的全局范围内的函数。

示例代码

$('#button1').on('click', function(e)
    // Determined action.
    update_html();
);

... // Around 75 more of this

function update_html() .... 

...

More example code

结论

我确实需要整理这段代码以供最佳使用,而不是重复自己,并且能够添加新功能和更新旧功能。我将自己解决这个问题。有些选择器可以是 100 行代码,而其他选择器是 1。我看过require.js 并发现它有点重复,实际上编写的代码比需要的多。我对任何符合此标准的可能解决方案持开放态度,并且链接到资源/示例总是一个加号。

谢谢。

【问题讨论】:

如果要添加backbone.js 和require.js 会很麻烦。 写这篇文章时,你发现自己一遍又一遍地做着什么? 你访问过codereview.stackexchange.com吗? 学习 Angular!这是未来。 您的代码不应位于外部链接,而应位于此处。此外,@codereview 是处理这类问题的更好地方。 【参考方案1】:

我将介绍一些简单的事情,这些事情可能对您有帮助,也可能对您没有帮助。有些可能很明显,有些可能非常神秘。

第 1 步:划分代码

将您的代码分成多个模块化单元是非常好的第一步。把“一起”工作的东西收集起来,把它们放在自己的小封闭单元中。现在不要担心格式,保持内联。结构是后一点。

所以,假设你有一个这样的页面:

为了便于维护(并且不必筛选 1000 行),将所有与标头相关的事件处理程序/绑定器都放在其中是有意义的。

然后您可以使用 Grunt 等工具将 JS 重新构建为单个单元。

步骤 1a:依赖管理

使用 RequireJS 或 CommonJS 等库来实现称为 AMD 的东西。异步模块加载允许您明确说明代码所依赖的内容,然后您可以将库调用卸载到代码中。您可以直接说“这需要 jQuery”,AMD 会加载它,并在当 jQuery 可用时执行您的代码

这也有一个隐藏的宝石:库加载将在 DOM 准备好后完成,而不是之前。这不再停止加载您的页面!

第 2 步:模块化

看到线框了吗?我有两个广告单元。他们很可能有共享的事件监听器。

您在此步骤中的任务是识别代码中的重复点,并尝试将所有这些综合到 模块 中。现在,模块将涵盖所有内容。我们会在进行过程中拆分内容。

这一步的整个想法是从第 1 步开始,删除所有的复制面,用松散耦合的单元替换它们。所以,而不是:

ad_unit1.js

 $("#au1").click(function()  ... );

ad_unit2.js

 $("#au2").click(function()  ... );

我会:

ad_unit.js:

 var AdUnit = function(elem) 
     this.element = elem || new jQuery();
 
 AdUnit.prototype.bindEvents = function() 
     ... Events go here
 

page.js:

 var AUs = new AdUnit($("#au1,#au2"));
 AUs.bindEvents();

除了消除重复之外,这还允许您在 事件标记 之间进行划分。这是一个相当不错的步骤,我们稍后会进一步扩展。

第 3 步:选择一个框架!

如果您想进一步模块化和减少重复,可以使用许多很棒的框架来实现 MVC(模型 - 视图 - 控制器)方法。我最喜欢的是 Backbone/Spine,不过,还有 Angular、Yii、……不胜枚举。

模型代表您的数据。

视图代表您的标记以及与之相关的所有事件

控制器代表您的业务逻辑 - 换句话说,控制器告诉页面要加载哪些视图以及要使用哪些模型。

这将是一个重要的学习步骤,但奖励是值得的:与意大利面条相比,它更喜欢干净、模块化的代码。

您还可以做很多其他事情,这些只是指导方针和想法。

特定于代码的更改

以下是对您的代码的一些具体改进:

 $('.new_layer').click(function()

    dialog("Create new layer","Enter your layer name","_input", 

            'OK' : function()

                    var reply = $('.dialog_input').val();

                    if( reply != null && reply != "" )

                            var name = "ln_"+reply.split(' ').join('_');
                            var parent = "";

                            if(selected_folder != "" )
                            parent = selected_folder+" .content";
                            

                            $R.find(".layer").clone()
                            .addClass(name).html(reply)
                            .appendTo("#layer_groups "+parent);

                            $R.find(".layers_group").clone()
                            .addClass(name).appendTo('#canvas '+selected_folder);

            

        

    );
 );

最好这样写:

$("body").on("click",".new_layer", function() 
    dialog("Create new layer", "Enter your layer name", "_input", 
         OK: function() 
             // There must be a way to get the input from here using this, if it is a standard library. If you wrote your own, make the value retrievable using something other than a class selector (horrible performance + scoping +multiple instance issues)

             // This is where the view comes into play. Instead of cloning, bind the rendering into a JS prototype, and instantiate it. It means that you only have to modify stuff in one place, you don't risk cloning events with it, and you can test your Layer stand-alone
             var newLayer = new Layer();
             newLayer
               .setName(name)
               .bindToGroup(parent);
          
     );
);

在您的代码前面:

window.Layer = function() 
    this.instance = $("<div>");
    // Markup generated here
;
window.Layer.prototype = 
   setName: function(newName) 
   ,
   bindToGroup: function(parentNode) 
   

突然之间,您可以在代码中的任何位置创建标准层,而无需复制粘贴。你在五个不同的地方做这件事。我刚刚为你保存了五个复制粘贴。

还有一个:

// 动作的规则集包装器

var PageElements = function(ruleSet) 
ruleSet = ruleSet || [];
this.rules = [];
for (var i = 0; i < ruleSet.length; i++) 
    if (ruleSet[i].target && ruleSet[i].action) 
        this.rules.push(ruleSet[i]);
    


PageElements.prototype.run = function(elem) 
for (var i = 0; i < this.rules.length; i++) 
    this.rules[i].action.apply(elem.find(this.rules.target));



var GlobalRules = new PageElements([

    "target": ".draggable",
    "action": function()  this.draggable(
        cancel: "div#scrolling, .content",
        containment: "document"
        );
    
,

    "target" :".resizable",
    "action": function() 
        this.resizable(
            handles: "all",
            zIndex: 0,
            containment: "document"
        );
    


]);

GlobalRules.run($("body"));

// If you need to add elements later on, you can just call GlobalRules.run(yourNewElement);

如果您有非标准事件或创建事件,这是注册规则的一种非常有效的方法。当与 pub/sub 通知系统结合使用时,当绑定到创建元素时触发的事件时,这也是非常糟糕的。 Fire'n'forget 模块化事件绑定!

【讨论】:

@Jessica:为什么在线工具应该有所不同?方法还是一样的:划分/模块化,使用框架促进组件之间的松散耦合(现在它们都带有事件委托),将代码分开。那里有什么适用于您的工具?你有很多按钮吗? @Jessica:已更新。我使用类似于View 的概念简化并简化了图层的创建。所以。这如何不适用于您的代码? @Jessica:在没有优化的情况下拆分成文件就像买更多的抽屉来存放垃圾。有一天,你必须清理干净,而且在抽屉装满之前清理更容易。为什么不两者都做?现在,看起来你会想要一个layers.jssidebar.jsglobal_events.jsresources.jsfiles.jsdialog.js,如果你只是要拆分你的代码。使用grunt 将它们重建为一个并使用Google Closure Compiler 进行编译和最小化。 在使用 require.js 时,您还必须真正研究 r.js 优化器,这确实是 require.js 值得使用的原因。它将合并和优化您的所有文件:requirejs.org/docs/optimization.html @SébastienRenauld 您的回答和 cmets 仍然受到其他用户的赞赏。如果这能让你感觉更好;)【参考方案2】:

这是一种使用 require.js 将当前代码库拆分为多个文件的简单方法。 我将向您展示如何将代码拆分为两个文件。之后添加更多文件将很简单。

第 1 步)在您的代码顶部,创建一个 App 对象(或您喜欢的任何名称,例如 MyGame):

var App =

第 2 步)将所有***变量和函数转换为属于 App 对象。

代替:

var selected_layer = "";

你想要:

App.selected_layer = "";

代替:

function getModified()
...

你想要:

App.getModified = function() 


请注意,此时您的代码将无法工作,直到您完成下一步。

第 3 步) 将所有全局变量和函数引用转换为通过 App。

更改如下内容:

selected_layer = "."+classes[1];

到:

App.selected_layer = "."+classes[1];

和:

getModified()

到:

App.GetModified()

第 4 步) 在这个阶段测试您的代码——它应该都能工作。一开始你可能会遇到一些错误,因为你错过了一些东西,所以在继续之前修复这些错误。

第 5 步) 设置 requirejs。我假设您有一个网页,由网络服务器提供服务,其代码位于:

www/page.html

和jquery在

www/js/jquery.js

如果这些路径不是像这样完全,则以下将不起作用,您必须修改路径。

下载requirejs 并将require.js 放到你的www/js 目录中。

在您的page.html 中,删除所有脚本标签并插入一个脚本标签,例如:

<script data-main="js/main" src="js/require.js"></script>

用内容创建www/js/main.js

require.config(
 "shim": 
   'jquery':  exports: '$' 
 
)

require(['jquery', 'app']);

然后将你刚刚在步骤 1-3 中修复的所有代码(其唯一的全局变量应该是 App)放入:

www/js/app.js

在该文件的最顶部,输入:

require(['jquery'], function($) 

在底部放置:

)

然后在浏览器中加载 page.html。您的应用应该可以运行!

第 6 步)创建另一个文件

这是您的工作得到回报的地方,您可以一遍又一遍地这样做。

www/js/app.js 中提取一些引用$ 和App 的代码。

例如

$('a').click(function()  App.foo() 

放入www/js/foo.js

在该文件的最顶部,输入:

require(['jquery', 'app'], function($, App) 

在底部放置:

)

然后把www/js/main.js的最后一行改成:

require(['jquery', 'app', 'foo']);

就是这样!每次您想将代码放在自己的文件中时都这样做!

【讨论】:

这有多个问题 - 一个明显的问题是,您在最后分割了所有文件,并迫使每个用户 每个脚本每次页面加载 400 字节的浪费数据通过不使用r.js 预处理器。此外,您实际上并没有解决 OP 的问题 - 只是提供了一个通用的 require.js howto。 嗯?我的回答是针对这个问题的。 r.js 显然是下一步,但这里的问题是组织,而不是优化。 我喜欢这个答案,我从未使用过 require.js,所以我必须看看我是否可以使用它并从中获得任何好处。我大量使用模块模式,但也许这可以让我将一些东西抽象出来,然后再引入它们。 @SébastienRenauld :这个答案不仅仅是关于 require.js。它主要是关于为您正在构建的代码提供命名空间。我认为您应该欣赏好的部分并进行编辑以发现任何问题。 :)【参考方案3】:

对于您的问题和 cmets,我假设您不愿意将代码移植到 Backbone 之类的框架,或者使用 Require 之类的加载器库。您只是想要一种更好的方法来以最简单的方式组织您已有的代码。

我知道滚动 2000 多行代码来查找您要处理的部分很烦人。解决方案是将您的代码拆分为不同的文件,每个文件对应一个功能。例如sidebar.jscanvas.js 等。然后你可以使用 Grunt 将它们连接在一起进行生产,与 Usemin 一起你可以有这样的东西:

在您的 html 中:

<!-- build:js scripts/app.js -->
<script src="scripts/sidebar.js"></script>
<script src="scripts/canvas.js"></script>
<!-- endbuild -->

在你的 Gruntfile 中:

useminPrepare: 
  html: 'app/index.html',
  options: 
    dest: 'dist'
  
,
usemin: 
  html: ['dist/,*/*.html'],
  css: ['dist/styles/,*/*.css'],
  options: 
    dirs: ['dist']
  

如果您想使用 Yeoman,它将为您提供所有这些的样板代码。

然后对于每个文件本身,您需要确保遵循最佳实践,并且所有代码和变量都在该文件中,并且不依赖于其他文件。这并不意味着您不能从另一个文件调用一个文件的函数,关键是要封装变量和函数。类似于命名空间的东西。我假设您不想将所有代码移植为面向对象,但如果您不介意重构一下,我建议添加与所谓的模块模式等效的东西。它看起来像这样:

sidebar.js

var Sidebar = (function()
// functions and vars here are private
var init = function()
  $("#sidebar #sortable").sortable(
            forceHelperSize: true,
            forcePlaceholderSize: true,
            revert: true,
            revert: 150,
            placeholder: "highlight panel",
            axis: "y",
            tolerance: "pointer",
            cancel: ".content"
       ).disableSelection();
   
  return 
   // here your can put your "public" functions
   init : init
  
)();

然后你可以像这样加载这段代码:

$(document).ready(function()
   Sidebar.init();
   ...

这将使您拥有更易于维护的代码,而无需过多地重写代码。

【讨论】:

您可能需要认真重新考虑倒数第二个 sn-p,这并不比内联编写代码更好:您的模块需要 #sidebar #sortable。您也可以通过内联代码并保存两个 IETF 来节省内存。 关键是你可以使用你需要的任何代码。我只是使用原始代码中的一个示例 我同意耶稣这只是一个例子,OP 可以轻松添加一个选项“对象”,允许他们指定元素的选择器而不是硬编码,但这只是一个快速例子。我想说我喜欢模块模式,它是我使用的主要模式,但即便如此,我仍在努力更好地组织我的代码。我通常使用 C#,所以函数命名和创建感觉很通用。我尝试保持一个“模式”,比如下划线是本地和私有的,变量只是“对象”,然后我在返回时引用该函数,它是公共的。 然而,我仍然发现这种方法存在挑战,并希望有更好的方法来做到这一点。但它比仅仅在全局空间中声明我的变量和函数来导致与其他 js 冲突的效果要好得多....lol【参考方案4】:

使用 javascript MVC 框架以标准方式组织 javascript 代码。

可用的最佳 JavaScript MVC 框架是:

Backbone Angular CanJS Ember ReactJS

选择 JavaScript MVC 框架需要考虑很多因素。阅读以下比较文章,它将帮助您根据对项目重要的因素选择最佳框架: http://sporto.github.io/blog/2013/04/12/comparison-angular-backbone-can-ember/

您还可以将RequireJS 与框架一起使用以支持异步js​​文件和模块加载。 看下面开始加载 JS 模块:http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/

【讨论】:

【参考方案5】:

对您的代码进行分类。这种方法对我有很大帮助,并且适用于任何 js 框架:

(function()//HEADER: menu
    //your code for your header
)();
(function()//HEADER: location bar
    //your code for your location
)();
(function()//FOOTER
    //your code for your footer
)();
(function()//PANEL: interactive links. e.g:
    var crr = null;
    $('::section.panel a').addEvent('click', function(E)
        if ( crr) 
            crr.hide();
        
        crr = this.show();
    );
)();

在您喜欢的编辑器中(最好的是 Komodo Edit),您可以通过折叠所有条目来折叠,您只会看到标题:

(function()//HEADER: menu_____________________________________
(function()//HEADER: location bar_____________________________
(function()//FOOTER___________________________________________
(function()//PANEL: interactive links. e.g:___________________

【讨论】:

+1 用于不依赖库的标准 JS 解决方案。 -1 有多种原因。您的等效代码与 OP 的完全一样... + 每个“部分”一个 IETF。此外,您正在使用过于宽泛的选择器,而不允许模块开发人员覆盖这些选择器的创建/删除或扩展行为。最后,IETF 不是免费的。 @hobberwickey:不了解你,但我宁愿依靠社区驱动的东西,如果可以的话,可以快速找到错误。特别是如果不这样做会谴责我重新发明***。 所做的只是将代码组织成离散的部分。上次我检查是 A:好的做法,B:不是你真正需要社区支持的库的东西。并非所有项目都适合 Backbone、Angular 等,通过将代码包装在函数中来模块化代码是一个很好的通用解决方案。 您可以随时依赖任何喜欢的库来使用这种方法。但上述解决方案适用于纯 javascript、自定义库或任何著名的 js 框架【参考方案6】:

我建议:

    用于事件管理的发布者/订阅者模式。 面向对象 命名空间

在您的情况下,杰西卡,将界面划分为页面或屏幕。页面或屏幕可以是对象并从某些父类扩展而来。使用 PageManager 类管理页面之间的交互。

【讨论】:

您能否通过示例/资源对此进行扩展? “面向对象”是什么意思? JS 中的几乎所有东西 都是 对象。并且 JS 中没有类。【参考方案7】:

我建议你使用 Backbone 之类的东西。 Backbone 是一个支持 RESTFUL 的 JavaScript 库。 ik 让你的代码更简洁,更易读,与 requirejs 一起使用时功能强大。

http://backbonejs.org/

http://requirejs.org/

Backbone 不是真正的库。它旨在为您的 javascript 代码提供结构。它能够包含其他库,如 jquery、jquery-ui、google-maps 等。在我看来,Backbone 是最接近面向对象和模型视图控制器结构的 javascript 方法。

还有关于您的工作流程。如果您使用 php 构建应用程序,请使用 Laravel 库。当与 RESTfull 原则一起使用时,它将与 Backbone 完美配合。下面是 Laravel 框架的链接和有关构建 RESTfull API 的教程:

http://maxoffsky.com/code-blog/building-restful-api-in-laravel-start-here/

http://laravel.com/

以下是来自 nettuts 的教程。 Nettuts 有很多高质量的教程:

http://net.tutsplus.com/tutorials/javascript-ajax/understanding-backbone-js-and-the-server/

【讨论】:

【参考方案8】:

也许是时候开始使用诸如 yeoman http://yeoman.io/ 之类的工具来实施整个开发工作流程了。这将有助于控制您的所有依赖项、构建过程,如果您愿意,还可以进行自动化测试。一开始需要做很多工作,但一旦实施,未来的变化就会变得容易得多。

【讨论】:

以上是关于组织 jQuery/JavaScript 代码的最佳方式(2013 年)[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Django 表单:为继承模型组织创建/更新表单的最 DRY 方式

《代码大全》阅读笔记-14-组织直线型代码

16 JQuery---JavaScript框架

jQuery/JavaScript:检测滚动方向 - 代码结构问题

Jquery/Javascript 追加不运行

Python —— 目录组织方式