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

Kiv*_*ius 102 javascript jquery design-patterns requirejs backbone.js

问题

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

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

示例代码

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

... // Around 75 more of this

function update_html(){ .... }

...
Run Code Online (Sandbox Code Playgroud)

更多示例代码

结论

我真的需要组织这个代码以便最好地使用,而不是重复自己,并能够添加新功能和更新旧功能.我将自己做这件事.一些选择器可以是100行代码,其他的是1.我看了一下,require.js发现它有点重复,实际上编写了比需要更多的代码.我对任何符合此标准的可能解决方案持开放态度,并且链接到资源/示例始终是一个优势.

谢谢.

Séb*_*uld 94

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

第1步:划分代码

将代码分成多个模块化单元是非常好的第一步.将"合在一起"的作品整理成自己的小包装单元.不要担心现在的格式,保持内联.结构是后来的一点.

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

在此输入图像描述

划分区域是有意义的,以便所有与标题相关的事件处理程序/绑定器都在那里,以便于维护(并且不必筛选1000行).

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

步骤1a:依赖管理

使用诸如RequireJS或CommonJS之类的库来实现称为AMD的东西.异步模块加载允许您明确说明代码所依赖的内容,然后允许您将库调用卸载到代码中.您可以简单地说"这需要jQuery",AMD将加载它,并在jQuery可用时执行您的代码.

这也有一个隐藏的宝石:库加载将在DOM准备好的第二个时间内完成,而不是之前.这不再会停止页面的加载!

第2步:模块化

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

您在此步骤中的任务是确定代码中的重复点,并尝试将所有这些合成到模块中.现在,模块将包含所有内容.随着我们的进展,我们会拆分东西.

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

ad_unit1.js

 $("#au1").click(function() { ... });
Run Code Online (Sandbox Code Playgroud)

ad_unit2.js

 $("#au2").click(function() { ... });
Run Code Online (Sandbox Code Playgroud)

我会有:

ad_unit.js:

 var AdUnit = function(elem) {
     this.element = elem || new jQuery();
 }
 AdUnit.prototype.bindEvents = function() {
     ... Events go here
 }
Run Code Online (Sandbox Code Playgroud)

page.js:

 var AUs = new AdUnit($("#au1,#au2"));
 AUs.bindEvents();
Run Code Online (Sandbox Code Playgroud)

除了摆脱重复之外,它还允许您在事件标记之间划分区域.这是一个相当不错的步骤,我们将在稍后进一步扩展.

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

            }

        }

    });
 });
Run Code Online (Sandbox Code Playgroud)

这写得更好:

$("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);
          }
     });
});
Run Code Online (Sandbox Code Playgroud)

在您的代码的前面:

window.Layer = function() {
    this.instance = $("<div>");
    // Markup generated here
};
window.Layer.prototype = {
   setName: function(newName) {
   },
   bindToGroup: function(parentNode) {
   }
}
Run Code Online (Sandbox Code Playgroud)

突然间,您可以从代码中的任何位置创建标准图层,而无需复制粘贴.你在五个不同的地方做这件事.我刚给你救了五个副本.

多一个:

//用于操作的规则集包装器

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);
Run Code Online (Sandbox Code Playgroud)

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

  • @Jessica:在没有优化的情况下拆分成文件就像购买更多抽屉来存放垃圾一样.有一天,你必须清理,在抽屉填满之前更容易清理.为什么不两个都做?现在,看起来你想要一个`layers.js`,`sidebar.js`,`global_events.js`,`resources.js`,`files.js`,`dialog.js`如果你只是要分割你的代码.使用`grunt`将它们重建为一个和`Google Closure Compiler`来编译和最小化. (10认同)
  • 当使用require.js时,你必须真正研究r.js优化器,这正是使得require.js值得使用的原因.它将结合并优化您的所有文件:http://requirejs.org/docs/optimization.html (3认同)
  • @Jessica:为什么在线工具会有所不同?方法仍然是相同的:区分/模块化,使用框架促进组件之间的松散耦合(这些天它们都带有事件委托),将代码分开.有什么不**适用于你的工具?你有很多按钮的事实? (2认同)
  • @Jessica:更新了.我使用类似于"View"的概念简化并简化了图层的创建.所以.这不适用于您的代码? (2认同)
  • @SébastienRenauld您的回答和评论仍然受到其他用户的赞赏.如果这可以让你感觉更好;) (2认同)

Lyn*_*ley 13

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

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

var App = {}

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

代替:

var selected_layer = "";

你要:

App.selected_layer = "";

代替:

function getModified(){
...
}
Run Code Online (Sandbox Code Playgroud)

你要:

App.getModified = function() {

}
Run Code Online (Sandbox Code Playgroud)

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

步骤3)转换所有全局变量和函数引用以通过App.

改变像:

selected_layer = "."+classes[1];
Run Code Online (Sandbox Code Playgroud)

至:

App.selected_layer = "."+classes[1];
Run Code Online (Sandbox Code Playgroud)

和:

getModified()
Run Code Online (Sandbox Code Playgroud)

至:

App.GetModified()
Run Code Online (Sandbox Code Playgroud)

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

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

www/page.html
Run Code Online (Sandbox Code Playgroud)

和jquery in

www/js/jquery.js
Run Code Online (Sandbox Code Playgroud)

如果这些路径不完全相同,则下面的方法不起作用,您必须修改路径.

下载requirejs并将require.js放在您的www/js目录中.

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

<script data-main="js/main" src="js/require.js"></script>
Run Code Online (Sandbox Code Playgroud)

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

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

require(['jquery', 'app']);
Run Code Online (Sandbox Code Playgroud)

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

www/js/app.js
Run Code Online (Sandbox Code Playgroud)

在该文件的最顶部,放置:

require(['jquery'], function($) {
Run Code Online (Sandbox Code Playgroud)

在底部放:

})
Run Code Online (Sandbox Code Playgroud)

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

步骤6)创建另一个文件

这是你的工作得到回报的地方,你可以一遍又一遍地做到这一点.

www/js/app.js引用$和App中拉出一些代码.

例如

$('a').click(function() { App.foo() }
Run Code Online (Sandbox Code Playgroud)

把它放进去 www/js/foo.js

在该文件的最顶部,放置:

require(['jquery', 'app'], function($, App) {
Run Code Online (Sandbox Code Playgroud)

在底部放:

})
Run Code Online (Sandbox Code Playgroud)

然后将www/js/main.js的最后一行更改为:

require(['jquery', 'app', 'foo']);
Run Code Online (Sandbox Code Playgroud)

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

  • 咦?我的回答是针对这个问题的.而r.js显然是下一步,但这里的问题是组织,而不是优化. (7认同)

Jes*_*era 10

对于您的问题和评论,我假设您不愿意将代码移植到像Backbone这样的框架,或者使用像Require这样的加载程序库.您只想以最简单的方式更好地讨论已有的代码.

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

在你的HTML中:

<!-- build:js scripts/app.js -->
<script src="scripts/sidebar.js"></script>
<script src="scripts/canvas.js"></script>
<!-- endbuild -->
Run Code Online (Sandbox Code Playgroud)

在你的Gruntfile中:

useminPrepare: {
  html: 'app/index.html',
  options: {
    dest: 'dist'
  }
},
usemin: {
  html: ['dist/{,*/}*.html'],
  css: ['dist/styles/{,*/}*.css'],
  options: {
    dirs: ['dist']
  }
}
Run Code Online (Sandbox Code Playgroud)

如果您想使用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
  }
})();
Run Code Online (Sandbox Code Playgroud)

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

$(document).ready(function(){
   Sidebar.init();
   ...
Run Code Online (Sandbox Code Playgroud)

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


Roh*_*lor 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/


归档时间:

查看次数:

42586 次

最近记录:

7 年,4 月 前