组织jQuery / JavaScript代码的最佳方式(2013)

问题

这个答案之前已经回答了,但是已经老了,没有最新。 我在单个文件中有超过2000行代码,而且我们都知道这是不好的做法,特别是当我查看代码或添加新function时。 我想更好地组织我的代码,现在和未来。

我应该提到,我正在构build一个工具(不是一个简单的网站),其中有许多button,UI元素,拖动,下降,动作监听器/处理程序和函数在全球范围内,其中几个听众可以使用相同的function。

示例代码

$('#button1').on('click', function(e){ // Determined action. update_html(); }); ... // Around 75 more of this function update_html(){ .... } ... 

更多示例代码

结论

我真的需要组织这个代码,以便最好的使用,而不是重复自己,并能够添加新的function,并更新旧的。 我将一个人在这个工作。 有些select器可以是100行代码,其他的是1.我看了一下require.js ,发现它有点重复性,实际上编写的代码比需要的多。 我愿意接受符合这个标准的任何可能的解决scheme,并且链接到资源/例子总是一个优点。

谢谢。

我会回顾一些简单的事情,可能会或可能不会帮助你。 有些可能是显而易见的,有些可能是非常神秘的。

第1步:分区您的代码

将代码分成多个模块化单元是非常好的第一步。 总结一下“一起工作”,并把它们放在他们自己的小包里。 现在不要担心格式,保持内联。 结构是稍后的一点。

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

在这里输入图像说明

为了便于维护(而不必筛选1000行),划分所有与头部相关的事件处理程序/活页夹在内部是有意义的。

然后,您可以使用Grunt等工具将您的JS重新构build回单个单元。

步骤1a:依赖pipe理

使用像RequireJS或CommonJS这样的库来实现一个叫做AMD的东西。 asynchronous模块加载允许您明确声明您的代码依赖于哪些内容,然后允许您将库调用卸载到代码中。 你可以从字面上说“这需要jQuery”,AMD会加载它,并在jQuery可用时执行你的代码。

这也有一个隐藏的gem:图书馆加载将完成第二个DOM准备好,而不是之前。 这不再停止加载您的网页!

第2步:模块化

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

这一步你的任务是确定代码中的重复点,并尝试将所有这些合成到模块中 。 模块,现在,将包括一切。 我们一起去分割东西。

这一步的总体思路是从第1步开始,删除所有的复制意见,用松散耦合的单元replace它们。 所以,而不是有:

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步:select一个框架!

如果你想模块化,甚至进一步减less重复,那么在实现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) { } } 

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

多一个:

//动作的Ruleset包装

 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); 

如果您有非标准事件或创build事件,则这是注册规则的一种非常有效的方法。 如果与发布/订阅通知系统结合使用,并且绑定到您创build元素时触发的事件,这也是严重的问题。 Fire'n'forget模块化的事件绑定!

以下是使用require.js将当前代码分割成多个文件的简单方法。 我将告诉你如何将你的代码分成两个文件。 之后添加更多的文件将很简单。

步骤1)在代码的顶部,创build一个App对象(或任何你喜欢的名字,比如MyGame):

var App = {}

步骤2)将所有顶级variables和函数转换为属于App对象。

代替:

var selected_layer = "";

你要:

App.selected_layer = "";

代替:

 function getModified(){ ... } 

你要:

 App.getModified = function() { } 

请注意,在这一点上,您的代码将不会工作,直到完成下一步。

步骤3)转换所有的全局variables和函数引用去通过应用程序。

改变的东西,如:

 selected_layer = "."+classes[1]; 

至:

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

和:

 getModified() 

至:

 App.GetModified() 

步骤4)在这个阶段testing你的代码 – 它应该都可以工作。 起初你可能会有一些错误,因为你错过了一些东西,所以在继续之前修正它们。

步骤5)设置requirejs。 我假设你有一个网页,从一个networking服务器,其代码是在:

 www/page.html 

和jquery中

 www/js/jquery.js 

如果这些path不完全是这样的下面将无法正常工作,你将不得不修改path。

下载requirejs并把你的www/js目录中的require.js。

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

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

用内容创buildwww/js/main.js

 require.config({ "shim": { 'jquery': { exports: '$' } } }) require(['jquery', 'app']); 

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

 www/js/app.js 

在该文件的最顶端,放:

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

底部说:

 }) 

然后在浏览器中加载page.html。 你的应用程序应该工作

第6步)创build另一个文件

这是你的工作得到回报的地方,你可以反复做这个。

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']); 

而已! 每次你想把代码放在自己的文件中时,都要这样做!

对于你的问题和评论,我会假设你不愿意将你的代码移植到像Backbone这样的框架,或者使用像Require这样的加载器库。 您只需要一个更好的方法来以最简单的方式来处理您已有的代码。

我知道,滚动浏览2000多行代码find你想要的部分是令人讨厌的。 解决scheme是将代码分成不同的文件,每个function一个。 例如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它会给你一个这样的样板代码。

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

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(); ... 

这将允许你有一个更可维护的代码,而不必重写你的代码太多。

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

最佳的JavaScript MVC框架是:

  • 骨干
  • angular
  • CanJS
  • 余烬

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

您也可以使用RequireJS框架来支持asynchronousJS文件和模块加载。
看看下面开始JS模块加载:
http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/

分类你的代码。 这个方法帮了我很多,并且可以和任何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. eg: var crr = null; $('::section.panel a').addEvent('click', function(E){ if ( crr) { crr.hide(); } crr = this.show(); }); })(); 

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

 (function(){//HEADER: menu_____________________________________ (function(){//HEADER: location bar_____________________________ (function(){//FOOTER___________________________________________ (function(){//PANEL: interactive links. eg:___________________ 

我会build议:

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

在你的情况下,杰西卡,将界面分成页面或屏幕。 页面或屏幕可以是对象,并可以从一些父类扩展。 使用PageManager类pipe理页面之间的交互。

我build议你使用Backbone之类的东西。 Backbone是一个RESTFUL支持的JavaScript库。 Ik使您的代码更清晰,更易读,与requirejs一起使用时function强大。

http://backbonejs.org/

http://requirejs.org/

骨干不是一个真正的图书馆。 这意味着给你的JavaScript代码结构。 它能够包括其他图书馆,如jQuery的,jQuery的,用户界面,谷歌地图等。在我看来,骨干是最接近的面向对象和模型视图控制器结构的JavaScript方法。

另外关于你的工作stream程..如果你使用PHP构build应用程序,使用Laravel库。 与RESTfull原理一起使用时,它将完美地与Backbone一起工作。 在链接到Laravel Framework的链接和有关构buildREST 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/

也许你的时间开始使用yeoman http://yeoman.io/等工具来实现整个开发工作stream程。; 这将有助于控制所有的依赖关系,构build过程,如果你想,自动化testing。 它开始的很多工作,但一旦实施,将使未来的变化更容易。