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


103

问题

这个答案以前已经被回答过,但是它是旧的并且不是最新的。我在一个文件中有2000行以上的代码,众所周知,这是一种不好的做法,尤其是当我浏览代码或添加新功能时。无论现在还是将来,我都希望更好地组织代码。

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

范例程式码

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

... // Around 75 more of this

function update_html(){ .... }

...

更多示例代码

结论

我确实需要组织此代码以使其最佳使用,而不是重复自己,并且能够添加新功能并更新旧功能。我将自己解决这个问题。有些选择器可能是100行代码,另一些则是1行。我看了一眼require.js,发现它有些重复,实际上编写了比所需更多的代码。我愿意接受任何符合此条件的解决方案,并且链接到资源/示例始终是一个加号。

谢谢。


如果要添加eleteger.js和require.js,将需要大量工作。
jantimon 2013年

1
您在撰写本文时一遍又一遍地发现自己在做什么任务?
Mike Samuel

4
您访问过codereview.stackexchange.com吗?
安东尼

4
学习角度!这是未来。
OnurYıldırım

2
您的代码不应位于外部链接,而应位于此处。同样,@ codereview是处理这类问题的更好的地方。
乔治·斯托克

Answers:


97

我将介绍一些可能会或可能不会对您有所帮助的简单事情。有些可能很明显,有些可能非常奥秘。

步骤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();

除了摆脱重复之外,它还使您可以在事件标记之间进行划分。这是一个相当不错的步骤,我们将在以后进一步扩展。

第三步:选择一个框架!

如果您想进一步模块化和减少重复,那么周围有很多很棒的框架可以实现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);

如果您有非标准事件或创建事件,这是注册规则的一种非常有效的方法。当与发布/订阅通知系统结合使用时,当绑定到事件时,无论何时创建元素,都将触发此事件。忘了模块化事件绑定!


2
@Jessica:为什么在线工具应该有所不同?方法仍然是相同的:划分/模块化,使用框架促进组件之间的松散耦合(这些天这些都随事件委托一起提供),将您的代码分开。还有什么,它适用于您的工具呢?你有很多按钮的事实吗?
塞巴斯蒂安Renauld

2
@Jessica:已更新。我使用类似于的概念简化并简化了层的创建View。所以。这如何不适用于您的代码?
塞巴斯蒂安Renauld

10
@杰西卡(Jessica):未经优化就拆分成文件就像买了更多抽屉来存放垃圾。有一天,您必须清理一下,在抽屉装满之前清理起来更容易。为什么不两者都做?现在,看起来你想layers.jssidebar.jsglobal_events.jsresources.jsfiles.jsdialog.js如果你只是要拆你的代码了。用于grunt将它们重建为一个并Google Closure Compiler进行编译和最小化。
塞巴斯蒂安Renauld

3
使用require.js时,您还必须真正研究r.js优化器,这正是使require.js值得使用的原因。它将合并并优化您的所有文件:requirejs.org/docs/optimization.html
Willem D'Haeseleer

2
@SébastienRenad您的答案和评论仍然会被其他用户赞赏。如果那可以使您感觉更好;)
阿德里安(Adrien

13

这是使用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。我假设您有一个由Web服务器提供的网页,其代码位于:

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
塞巴斯蒂安Renauld

7
??我的回答是针对这个问题的。r.js显然是下一步,但是这里的问题是组织,而不是优化。
Lyn Headley

我喜欢这个答案,我从未使用过require.js,因此我将不得不看看是否可以使用它并从中获得任何好处。我大量使用了模块模式,但是也许这会让我抽象出一些东西,然后再要求它们
Tony

1
@SébastienRenauld:这个答案不仅仅是关于require.js。它主要讲的是为要构建的代码提供名称空间。我认为您应该赞赏这些出色的部分,并进行编辑,发现有任何问题。:)
mithunsatheesh 2014年

10

对于您的问题和评论,我假设您不愿意将代码移植到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();
   ...

这将使您拥有更多可维护的代码,而不必过多重写代码。


1
您可能需要认真考虑一下倒数第二个代码段,这比内联编写代码更好:模块需要#sidebar #sortable。您也可以只通过内联代码并保存两个IETF来节省内存。
塞巴斯蒂安Renauld

关键是您可以使用所需的任何代码。我只是用从原代码的例子
赫苏斯·卡雷拉

我同意耶稣的观点,这只是一个示例,OP可以轻松添加一个选项“对象”,使他们可以指定元素的选择器,而不必对其进行硬编码,但这只是一个简单的示例。我想说我喜欢模块模式,这是我使用的主要模式,但是即使如此,我仍在尝试更好地组织代码。我通常使用C#,因此函数命名和创建感觉如此通用。我尝试保持“模式”,例如下划线是局部的和私有的,变量只是“对象”,然后在返回的引用中引用该函数,该函数是公共的。
托尼

但是,我仍然用这种方法发现挑战,并希望有一种更好的方法来实现。但是它比只声明我的变量和函数在全局空间引起与其他js的冲突要好得多....哈哈
Tony

6

使用javascript MVC Framework以便以标准方式组织javascript代码。

可用的最佳JavaScript MVC框架是:

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

您还可以在框架中使用RequireJS以支持Asynchrounous js文件和模块加载。
查看以下内容以开始JS模块加载:http :
//www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/


4

分类您的代码。此方法对我有很大帮助,并且可以与任何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:___________________

2
+1表示不依赖库的标准JS解决方案。
hobberwickey

-1有多种原因。您的等效代码与OP的代码完全相同... +每个“部分”一个IETF。此外,您使用的选择器过于广泛,而模块开发人员却无法覆盖这些选择器的创建/删除或扩展其行为。最后,IETF不是免费的。
塞巴斯蒂安Renauld

@hobberwickey:不了解您,但是我宁愿依靠社区驱动的东西,如果可以的话,可以在哪里快速发现错误。特别是如果这样做否则会谴责我重新发明轮子。
塞巴斯蒂安·雷纳尔德

2
所有要做的就是将代码组织到不连续的部分中。上次我检查的是A:​​良好实践,B:您确实不需要社区支持的库。并非所有项目都适合Backbone,Angular等,通过将代码包装在函数中来模块化代码是一个很好的通用解决方案。
hobberwickey

您随时可以依赖任何喜欢的库来使用此方法。但是上述解决方案可与纯JavaScript,自定义库或任何著名的js框架一起使用

3

我会建议:

  1. 事件管理的发布者/订阅者模式。
  2. 面向对象
  3. 命名空间

对于您的情况Jessica,请将界面分为页面或屏幕。页面或屏幕可以是对象,也可以是某些父类的扩展。使用PageManager类管理页面之间的交互。


您可以使用示例/资源对此进行扩展吗?
基伍利斯,

1
“面向对象”是什么意思?JS 的几乎所有东西都是对象。JS中没有类。
Bergi 2013年

2

我建议您使用类似Backbone的工具。Backbone是RESTFUL支持的javascript库。Ik使您的代码更清晰,更易读,并且与requirejs一起使用时功能强大。

http://backbonejs.org/

http://requirejs.org/

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

也与您的工作流程有关。如果您使用PHP构建应用程序,请使用Laravel库。与RESTfull原理一起使用时,它将与Backbone完美配合。在链接到Laravel Framework的链接和有关构建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/


0

也许是时候开始使用yeoman http://yeoman.io/之类的工具来实现整个开发流程了。这将有助于控制所有依赖项,构建过程以及自动测试(如果需要)。首先要进行很多工作,但是一旦实施,将来的更改将变得更加容易。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.