Commonly accepted best practices around code organization in JavaScript
像JQuery这样的JavaScript框架使客户端Web应用程序更加丰富和功能化,我开始注意到一个问题…
你到底怎么保持这个组织的?
- 将所有处理程序放在一个位置,并为所有事件编写函数?
- 创建函数/类来包装所有功能?
- 写得像疯了一样,只希望能达到最好?
- 放弃并获得新的职业?
我提到了jquery,但它实际上是所有的javascript代码。我发现,随着一行接一行的内容开始堆积,管理脚本文件或查找所需内容变得越来越困难。很可能我发现的最大的问题是有这么多的方法来做同样的事情,很难知道哪一个是目前普遍接受的最佳实践。
对于如何保持.js文件与应用程序的其余部分一样美观和整洁,有没有什么一般性的建议?或者这只是IDE的问题?有更好的选择吗?
编辑
这个问题更多的是关于代码组织,而不是文件组织。有一些很好的例子可以合并文件或分割内容。
我的问题是:目前公认的组织实际代码的最佳实践方法是什么?您的方法是什么,甚至是建议的与页面元素交互并创建互不冲突的可重用代码的方法?
有些人列出了名称空间,这是一个好主意。还有什么其他的方法,更具体地说是处理页面上的元素并保持代码的有序和整洁?
如果Javascript内置了名称空间,那就更好了,但是我发现像DustinDiaz这样的组织可以帮助我很多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var DED = (function() { var private_var; function private_method() { // do stuff here } return { method_1 : function() { // do stuff here }, method_2 : function() { // do stuff here } }; })(); |
我把不同的"名称空间"和一些单独的类放在不同的文件中。通常我从一个文件开始,当一个类或名称空间变得足够大以保证它的存在时,我将它分离成自己的文件。使用一个工具来组合所有的生产文件也是一个好主意。
我尽量避免使用HTML中包含任何JavaScript。所有代码都封装在类中,每个类都在自己的文件中。对于开发,我有单独的标签来包括每个JS文件,但是它们被合并到一个更大的包中用于生产,以减少HTTP请求的开销。
通常,每个应用程序都有一个"主"JS文件。所以,如果我正在写一个"调查"应用程序,我会有一个名为"Dealth.js"的JS文件。这将包含进入jQuery代码的入口点。在实例化过程中创建jQuery引用,然后将它们作为参数传递到对象中。这意味着JavaScript类是"纯"的,不包含任何对CSS ID或类名的引用。
1 2 3 4 5 6 7 8 | // file: survey.js $(document).ready(function() { var jS = $('#surveycontainer'); var jB = $('#dimscreencontainer'); var d = new DimScreen({container: jB}); var s = new Survey({container: jS, DimScreen: d}); s.show(); }); |
我还发现命名约定对于可读性很重要。例如:我为所有jquery实例预先准备了"j"。
在上面的示例中,有一个名为dimscreen的类。(假设这会使屏幕变暗并弹出一个警报框。)它需要一个DIV元素,它可以放大以覆盖屏幕,然后添加一个警报框,所以我传入一个jquery对象。jquery有一个插件概念,但它似乎有局限性(例如实例不是持久的,不能访问),没有真正的优势。所以dimscreen类是一个标准的javascript类,恰好使用jquery。
1 2 3 4 5 6 7 8 9 10 11 12 | // file: dimscreen.js function DimScreen(opts) { this.jB = opts.container; // ... }; // need the semi-colon for minimizing! DimScreen.prototype.draw = function(msg) { var me = this; me.jB.addClass('fullscreen').append(''+msg+''); //... }; |
我使用这种方法构建了一些相当复杂的应用程序。
您可以将脚本分解成单独的文件进行开发,然后创建一个"发布"版本,将它们全部塞进其中,并在上面运行yui压缩器或类似的东西。
受早期文章的启发,我制作了一份与Wyshiat(changelog提到的一个rte)一起分发的rakefile和供应商目录的副本,并做了一些修改,包括使用jslint检查代码和使用yui压缩器缩小代码。
其思想是使用链轮(从wyshiat)将多个javascript合并到一个文件中,使用jslint检查合并文件的语法,并在分发之前使用yui压缩器将其缩小。
先决条件
- Java运行时
- 红宝石和耙子宝石
- 你应该知道如何把一个jar放到类路径中
现在做
现在在javascript项目的根目录中创建一个名为"rakefile"的文件,并向其中添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | require 'rake' ROOT = File.expand_path(File.dirname(__FILE__)) OUTPUT_MERGED ="final.js" OUTPUT_MINIFIED ="final.min.js" task :default => :check desc"Merges the JavaScript sources." task :merge do require File.join(ROOT,"vendor","sprockets") environment = Sprockets::Environment.new(".") preprocessor = Sprockets::Preprocessor.new(environment) %w(main.js).each do |filename| pathname = environment.find(filename) preprocessor.require(pathname.source_file) end output = preprocessor.output_file File.open(File.join(ROOT, OUTPUT_MERGED), 'w') { |f| f.write(output) } end desc"Check the JavaScript source with JSLint." task :check => [:merge] do jslint_path = File.join(ROOT,"vendor","jslint.js") sh 'java', 'org.mozilla.javascript.tools.shell.Main', jslint_path, OUTPUT_MERGED end desc"Minifies the JavaScript source." task :minify => [:merge] do sh 'java', 'com.yahoo.platform.yui.compressor.Bootstrap', '-v', OUTPUT_MERGED, '-o', OUTPUT_MINIFIED end |
如果一切都正确,您应该能够在控制台中使用以下命令:
rake merge —将不同的javascript文件合并为一个文件rake check --检查代码的语法(这是默认任务,因此只需键入rake )。rake minify --准备JS代码的小型版本
关于源合并
使用链轮,您可以包括(或
1 2 3 4 5 6 7 | (function() { //= require"subdir/jsfile.js" //= require"anotherfile.js" // some code that depends on included files // note that all included files can be in the same private scope })(); |
然后。。。
看看Wyshiat提供的rakefile,设置自动单元测试。好东西:
现在来回答
这并不能很好地回答最初的问题。我知道这一点,我很抱歉,但我已经把它贴在这里了,因为我希望它对其他人组织他们的混乱会有所帮助。
我解决这个问题的方法是尽可能多地进行面向对象的建模,并将实现分为不同的文件。那么处理程序应该尽可能短。使用
和命名空间…它们可以被更深的物体结构所模仿。
1 2 3 4 5 6 7 8 9 10 11 | if (typeof org === 'undefined') { var org = {}; } if (!org.hasOwnProperty('example')) { org.example = {}; } org.example.AnotherObject = function () { // constructor body }; |
我不太喜欢模仿,但是如果你有很多想要从全球范围外转移的对象,这会很有帮助。
代码组织要求采用约定和文档标准:
1。物理文件的命名空间代码;
1 | Exc = {}; |
2。在这些命名空间中对类进行分组javascript;
三。设置原型或相关函数或类以表示现实世界中的对象;
1 2 3 4 5 6 7 8 9 10 11 | Exc = {}; Exc.ui = {}; Exc.ui.maskedInput = function (mask) { this.mask = mask; ... }; Exc.ui.domTips = function (dom, tips) { this.dom = gift; this.tips = tips; ... }; |
< BR>4。设置约定以改进代码。例如,在对象类型的class属性中对其所有内部函数或方法进行分组。
1 2 3 4 5 6 7 8 9 10 11 12 13 | Exc.ui.domTips = function (dom, tips) { this.dom = gift; this.tips = tips; this.internal = { widthEstimates: function (tips) { ... } formatTips: function () { ... } }; ... }; |
5。制作名称空间、类、方法和变量的文档。必要时还讨论一些代码(一些FIS和FORS,它们通常实现代码的重要逻辑)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /** * Namespace Example created to group other namespaces of the"Example". */ Exc = {}; /** * Namespace ui created with the aim of grouping namespaces user interface. */ Exc.ui = {}; /** * Class maskdInput used to add an input HTML formatting capabilities and validation of data and information. * @ Param {String} mask - mask validation of input data. */ Exc.ui.maskedInput = function (mask) { this.mask = mask; ... }; /** * Class domTips used to add an HTML element the ability to present tips and information about its function or rule input etc.. * @ Param {String} id - id of the HTML element. * @ Param {String} tips - tips on the element that will appear when the mouse is over the element whose identifier is id . */ Exc.ui.domTips = function (id, tips) { this.domID = id; this.tips = tips; ... }; |
< BR>这些只是一些提示,但这对代码的组织有很大帮助。记住,你必须有纪律才能成功!
遵循良好的面向对象设计原则和设计模式有助于使代码易于维护和理解。但我最近发现的最好的东西之一是信号和时隙,即发布/订阅。看看HTTP://MulkdotMyEr.BogSPo.com /No8/09/jQuay-Pusisier-SubEu.HTML。对于一个简单的jQuery实现。
该思想在其他语言中很好地用于GUI开发。当代码中某个地方发生重大事件时,发布一个全局合成事件,其他对象中的其他方法可能订阅该事件。这提供了优秀的对象分离。
我认为Dojo(和原型?)有这个技术的内置版本。
另请参见什么是信号和插槽?
在我之前的工作中,我成功地将javascript模块模式应用到ExtJS应用程序。它提供了一种创建良好封装代码的简单方法。
Dojo从第一天起就拥有了模块系统。事实上,它被认为是Dojo的基石,它是将Dojo粘合在一起的胶水:
- Dojo.需要正式文件。
- 了解dojo.declare、dojo.require和dojo.provide。
- 介绍Dojo。
使用模块Dojo可实现以下目标:
- Dojo代码和自定义代码(
dojo.declare() 的名称空间不污染全局空间,与其他库和用户的不支持Dojo的代码共存。 - 按名称同步或异步加载模块(
dojo.require() )。 - 通过分析模块依赖性来定制构建,以创建单个文件或一组相互依赖的文件(所谓的层),从而只包含Web应用程序需要的内容。定制的构建还可以包括Dojo模块和客户提供的模块。
- 对Dojo和用户代码的透明的基于cdn的访问。AOL和Google都以这种方式支持Dojo,但有些客户也会为他们的定制Web应用程序这样做。
我的老板还谈到他们编写模块化代码(C语言)的时候,抱怨现在代码有多糟糕!据说程序员可以在任何框架中编写程序集。总是有一种策略可以克服代码组织。最基本的问题是那些把Java脚本当作玩具的人,从不尝试去学习它。
在我的例子中,我使用适当的init_screen()在UI主题或应用程序屏幕的基础上编写JS文件。使用适当的ID命名约定,我确保根元素级别上没有名称空间冲突。在unobstrussivewindow.load()中,我根据顶级ID绑定这些内容。
我严格使用Java脚本闭包和模式来隐藏所有私有方法。这样做之后,就不会遇到属性/函数定义/变量定义冲突的问题。但是,在与团队合作时,通常很难执行相同的严格性。
我很惊讶没有人提到MVC框架。我一直在使用主干.js来模块化和分离我的代码,这是非常宝贵的。
现在有很多这样的框架,其中大部分也很小。我个人的观点是,如果你要写的不仅仅是几行jquery来获取华丽的UI内容,或者想要一个丰富的Ajax应用程序,MVC框架会让你的生活更轻松。
查看javascriptmvc。
你可以:
将代码拆分为模型、视图和控制器层。
将所有代码压缩到单个生产文件中
自动生成代码
创建和运行单元测试
还有更多…
最重要的是,它使用jquery,所以您也可以利用其他jquery插件。
"写得像疯了一样,只希望能达到最好的效果?"我见过这样一个项目,由两个开发人员开发和维护,一个拥有大量JavaScript代码的巨大应用程序。除此之外,您可以想到的每个jquery函数都有不同的快捷方式。我建议他们将代码组织为插件,因为这是类、模块、名称空间的jquery等价物…以及整个宇宙。但是事情变得更糟了,现在他们开始编写插件来替换项目中使用的每3行代码的组合。我个人认为jquery是个魔鬼,它不应该用在有很多javascript的项目上,因为它鼓励你懒惰,并且不考虑以任何方式组织代码。我宁愿阅读100行javascript,而不是一行40个链接的jquery函数(我不是开玩笑)。与人们普遍认为的相反,将JavaScript代码组织为与名称空间和类等效的代码非常容易。这就是Yui和Dojo所做的。如果你愿意,你可以很容易地自己滚。我发现Yui的方法更有效。但是如果你想写一些有用的东西,你通常需要一个支持代码片段的优秀编辑器来补偿yui命名约定。
我为每一件我真正不需要在屏幕上实例化几次的事情创建了一个单例,一个用于其他所有事情的类。它们都放在同一个文件中的同一个命名空间中。所有东西都被注释,并用UML、状态图设计。javascript代码没有HTML,因此没有内联javascript,我倾向于使用jquery来最小化跨浏览器问题。
在我上一个项目viajeros.com中,我使用了多种技术的组合。我不知道如何组织一个网络应用程序——Viajeros是一个为旅游者提供的社交网站,它有明确的部分,所以很容易将每个区域的代码分开。
我根据站点部分使用名称空间模拟和模块的延迟加载。在每个页面加载中,我声明一个"vjr"对象,并始终向它加载一组公共函数(vjr.base.js)。然后,每个HTML页面通过一个简单的:
1 | vjr.Required = ["vjr.gallery","vjr.comments","vjr.favorites"]; |
vjr.base.js从服务器获取每个gzip并执行它们。
1 2 3 4 5 6 7 8 9 10 11 | vjr.include(vjr.Required); vjr.include = function(moduleList) { if (!moduleList) return false; for (var i = 0; i < moduleList.length; i++) { if (moduleList[i]) { $.ajax({ type:"GET", url: vjr.module2fileName(moduleList[i]), dataType:"script" }); } } }; |
每个"模块"都有这样的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | vjr.comments = {} vjr.comments.submitComment = function() { // do stuff } vjr.comments.validateComment = function() { // do stuff } // Handlers vjr.comments.setUpUI = function() { // Assign handlers to screen elements } vjr.comments.init = function () { // initialize stuff vjr.comments.setUpUI(); } $(document).ready(vjr.comments.init); |
鉴于我有限的javascript知识,我知道必须有更好的方法来管理它,但直到现在它对我们来说都很有用。
以jquery为中心的名称空间方式组织代码可能如下所示…也不会与其他JavaScriptAPI的原型(比如ext)发生冲突。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <script src="jquery/1.3.2/jquery.js" type="text/javascript"> <script type="text/javascript"> var AcmeJQ = jQuery.noConflict(true); var Acme = {fn: function(){}}; (function($){ Acme.sayHi = function() { console.log('Hello'); }; Acme.sayBye = function() { console.log('Good Bye'); }; })(AcmeJQ); // Usage // Acme.sayHi(); // or // Say Hello |
希望这有帮助。
OO+MVC的良好原则对于管理复杂的JavaScript应用程序肯定会有很大的帮助。好的。
基本上,我将我的应用程序和JavaScript组织到以下熟悉的设计(从我的桌面编程时代一直到Web2.0)好的。
好的。
图像上数值的说明:好的。
在过去,我将把文件分成自己的JS,并使用常见的实践在JavaScript中创建OO原则。我很快发现有多种方法可以编写JSOO,但并不一定所有团队成员都有相同的方法。由于团队规模越来越大(在我的例子中,超过15人),这变得复杂起来,因为没有面向对象的JavaScript的标准方法。同时,我不想写我自己的框架,重复一些我确信比我解决的更聪明的人所做的工作。好的。
jquery作为javascript框架非常好,我喜欢它,但是随着项目的不断扩大,我显然需要为我的web应用程序增加额外的结构,特别是为了促进标准化的OO实践。对我自己来说,经过几次实验,我发现yui3基础和小部件(http://yuilibrary.com/yui/docs/widget/和http://yuilibrary.com/yui/docs/base/index.html)基础设施提供了我所需要的。我使用它们的原因不多。好的。
与许多视图相反,我不必在jquery和yui3之间进行选择。这两者可以和平共存。虽然yui3为我的复杂Web应用程序提供了必要的OO模板,但jquery仍然为我的团队提供了易于使用的JS抽象,我们都喜欢并熟悉它。好的。
通过使用YII3,我已经成功地创建了MVC模式,它将扩展基类的类作为模型,将窗口小部件扩展为视图,当然也可以使用具有必要逻辑和服务器端调用的控制器类。好的。
WIDGET可以使用基于事件的模型进行通信,并根据预先定义的接口执行事件并执行必要的任务。简单地说,把OO+MVC结构放到JS中对我来说是一种乐趣。好的。
只是免责声明,我不为雅虎工作!简单地说,一个架构师正试图处理与原始问题相同的问题。我认为如果有人找到等效的OO框架,这也会起作用。主要地,这个问题也适用于其他技术。感谢上帝,感谢所有提出OO原则+MVC的人,让我们的编程更加容易管理。好的。好啊。
我使用Dojo的包管理(
创建假类,并确保能够被抛出到一个有意义的单独函数中的任何事情都是这样做的。另外,一定要多加评论,不要编写spagghetti代码,而是将其全部保存在各个部分中。例如,一些描述我理想的无意义代码。显然,在现实生活中,我还编写了许多基本上包含其功能的库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | $(function(){ //Preload header images $('a.rollover').preload(); //Create new datagrid var dGrid = datagrid.init({width: 5, url: 'datalist.txt', style: 'aero'}); }); var datagrid = { init: function(w, url, style){ //Rendering code goes here for style / width //code etc //Fetch data in $.get(url, {}, function(data){ data = data.split(' '); for(var i=0; i < data.length; i++){ //fetching data } }) }, refresh: function(deep){ //more functions etc. } }; |
使用继承模式组织大型jquery应用程序。
几天前,37号信号的人发布了一个RTE控制,但有点扭曲。他们制作了一个库,使用某种预处理器命令捆绑JavaScript文件。
从那以后,我一直在使用它来分离JS文件,最后将它们合并为一个文件。这样,我就可以分离关注点,最后,只有一个文件通过管道(不少于gzip)。
在您的模板中,检查您是否处于开发模式,是否包含单独的文件,如果在生产中,则包括最终的文件(您必须自己"构建")。
我认为这可能与DDD(域驱动设计)有关。我正在开发的应用程序虽然没有正式的API,但通过服务器端代码(类/文件名等)确实给出了类似的提示。有了这些,我创建了一个顶级对象作为整个问题域的容器;然后,我在需要的地方添加了名称空间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | var App; (function() { App = new Domain( 'test' ); function Domain( id ) { this.id = id; this.echo = function echo( s ) { alert( s ); } return this; } })(); // separate file (function(Domain) { Domain.Console = new Console(); function Console() { this.Log = function Log( s ) { console.log( s ); } return this; } })(App); // implementation App.Console.Log('foo'); |
对于javascript组织,一直使用以下
我在用这个小东西。它为JS和HTML模板提供了"include"指令。它彻底消除了混乱。
https://github.com/gaperton/include.js网站/
1 2 3 4 5 6 7 8 9 10 11 12 13 | $.include({ html:"my_template.html" // include template from file... }) .define( function( _ ){ // define module... _.exports = function widget( $this, a_data, a_events ){ // exporting function... _.html.renderTo( $this, a_data ); // which expands template inside of $this. $this.find("#ok").click( a_events.on_click ); // throw event up to the caller... $this.find("#refresh").click( function(){ widget( $this, a_data, a_events ); // ...and update ourself. Yep, in that easy way. }); } }); |
您可以使用jquery mx(在javascriptmvc中使用),它是一组脚本,允许您使用模型、视图和控制器。我在一个项目中使用了它,并帮助我创建了结构化的JavaScript,由于压缩,脚本大小最小。这是一个控制器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $.Controller.extend('Todos',{ ".todo mouseover" : function( el, ev ) { el.css("backgroundColor","red") }, ".todo mouseout" : function( el, ev ) { el.css("backgroundColor","") }, ".create click" : function() { this.find("ol").append("<li class='todo'>New Todo </li> "); } }) new Todos($('#todos')); |
如果您对视图和模型部件不感兴趣,也只能使用jquerymx的控制器端。
你的问题是去年年底困扰我的。区别在于,将代码交给从未听说过私有和公共方法的新开发人员。我得做些简单的东西。
最终的结果是一个小的(大约1KB)框架,它将对象文本转换为jquery。语法在视觉上更容易扫描,如果您的JS变得非常大,您可以编写可重用的查询来查找所使用的选择器、加载的文件、依赖函数等。
在这里发布一个小框架是不切实际的,所以我写了一篇带有示例的博客文章(我的第一篇)。那是一次冒险!)欢迎你来看看。
对于这里的任何其他人,只要几分钟时间来检查一下,我将非常感谢您的反馈!
建议使用firefox,因为它支持对象查询示例的tosource()。
干杯!
亚当
您没有提到您的服务器端语言是什么。或者,更确切地说,您正在服务器端使用什么框架(如果有的话)。
输入法,我在服务器端组织事情,让它在网页上全部震动。框架的任务不仅是组织每个页面必须加载的JS,还包括处理生成的标记的JS片段。这样的片段通常不希望发出不止一次——这就是为什么它们被抽象到框架中,以便代码处理该问题。-)
对于必须发布自己的JS的端页,我通常发现在生成的标记中有逻辑结构。这样的本地化JS常常可以在这样的结构的开始和/或结束时被组装。
请注意,这些都不能让你编写高效的JavaScript!-)
我使用一个受Ben Nolan行为启发的自定义脚本来存储我的大多数事件处理程序(很遗憾,我再也找不到指向这个的当前链接)。例如,这些事件处理程序由元素classname或id触发。例子:
1 2 3 4 5 6 7 8 9 10 | Behaviour.register({ 'a.delete-post': function(element) { element.observe('click', function(event) { ... }); }, 'a.anotherlink': function(element) { element.observe('click', function(event) { ... }); } }); |
除了包含全局行为的JavaScript库外,我喜欢动态地包含大多数JavaScript库。我使用Zend框架的headscript()placeholder helper来实现这一点,但是您也可以使用javascript来动态加载Ajile中的其他脚本。
懒惰地按需加载所需的代码。google用他们的google.loader做了类似的事情。