随着像jQuery这样的JavaScript框架使客户端web应用程序更丰富,功能更强大,我开始注意到一个问题…

你到底是怎么组织起来的?

把所有的处理程序放在一个地方,并为所有事件编写函数? 创建函数/类来包装您的所有功能? 疯狂地写作,只希望结果是最好的? 放弃,找一份新工作?

我提到了jQuery,但它实际上是一般的JavaScript代码。我发现,当一行一行开始堆积时,管理脚本文件或找到您要找的内容变得越来越困难。我发现的最大问题可能是,做同一件事有太多的方法,很难知道哪一种是目前普遍接受的最佳实践。

有什么通用的建议可以让你的.js文件像你的应用程序的其他部分一样漂亮整洁吗?或者这只是IDE的问题?还有更好的选择吗?


EDIT

这个问题主要是关于代码组织,而不是文件组织。有一些合并文件或拆分内容的好例子。

我的问题是:目前普遍接受的组织实际代码的最佳实践方式是什么?您的方法是什么,甚至推荐的方法是什么,以与页面元素交互并创建互不冲突的可重用代码?

有些人列出了名称空间,这是个好主意。还有什么其他方法,更具体地说,处理页面上的元素并保持代码的组织和整洁?


当前回答

几天前,37Signals的人发布了一个RTE控件。他们制作了一个库,使用一种预处理器命令捆绑javascript文件。

我一直在用它来分离我的JS文件,然后在最后合并为一个。这样我就可以分离关注点,并且最终只有一个文件通过管道(gzip的)。

在模板中,检查是否处于开发模式,并包含单独的文件,如果处于生产模式,则包括最后一个文件(您必须自己“构建”)。

其他回答

在我的上一个项目- viajeros.com -我使用了几种技术的组合。我不知道如何组织一个网页应用程序——Viajeros是一个面向旅行者的社交网站,它有明确定义的部分,所以很容易将每个部分的代码分开。

我根据站点部分使用名称空间模拟和延迟加载模块。在每个页面加载我声明一个“vjr”对象,并总是加载一组常用函数到它(vjr.base.js)。然后每个HTML页面用简单的语句决定哪些模块需要:

vjr.Required = ["vjr.gallery", "vjr.comments", "vjr.favorites"];

Vjr.base.js从服务器获取并执行每一个gzip文件。

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

每个“模块”都有这样的结构:

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知识,我知道一定有更好的方法来管理这个,但到目前为止,它为我们工作得很好。

Dojo从一开始就使用模块系统。事实上,它被认为是Dojo的基石,是将所有Dojo结合在一起的粘合剂:

dojo。要求——官方文件。 理解dojo.declare, dojo。Require和dojo. provider。 引入Dojo。

使用模块Dojo可以实现以下目标:

Namespaces for Dojo code and custom code (dojo.declare()) — do not pollute the global space, coexist with other libraries, and user's non-Dojo-aware code. Loading modules synchronously or asynchronously by name (dojo.require()). Custom builds by analyzing module dependencies to create a single file or a group of interdependent files (so-called layers) to include only what your web application needs. Custom builds can include Dojo modules and customer-supplied modules as well. Transparent CDN-based access to Dojo and user's code. Both AOL and Google carry Dojo in this fashion, but some customers do that for their custom web applications as well.

代码组织要求采用约定和文档标准: 1. 物理文件的命名空间代码;

Exc = {};

2. 在这些命名空间javascript中分组类; 3.设置原型或相关的函数或类来表示真实世界的对象;

Exc = {};
Exc.ui = {};
Exc.ui.maskedInput = function (mask) {
    this.mask = mask;
    ...
};
Exc.ui.domTips = function (dom, tips) {
    this.dom = gift;
    this.tips = tips;
    ...
};

4. 设置约定以改进代码。例如,将其所有内部函数或方法分组在对象类型的class属性中。

Exc.ui.domTips = function (dom, tips) {
    this.dom = gift;
    this.tips = tips;
    this.internal = {
        widthEstimates: function (tips) {
            ...
        }
        formatTips: function () {
            ...
        }
    };
    ...
};

5. 制作名称空间、类、方法和变量的文档。必要时也讨论一些代码(一些fi和for,它们通常实现代码的重要逻辑)。

/**
  * Namespace <i> Example </i> created to group other namespaces of the "Example".  
  */
Exc = {};
/**
  * Namespace <i> ui </i> created with the aim of grouping namespaces user interface.
  */
Exc.ui = {};

/**
  * Class <i> maskdInput </i> 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 <i> domTips </i> 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 <i> </i>.
  */
  Exc.ui.domTips = function (id, tips) {
    this.domID = id;
    this.tips = tips;
    ...
};

这些只是一些技巧,但这对组织代码有很大帮助。记住,你必须自律才能成功!

我认为这可能与DDD(领域驱动设计)有关。我正在开发的应用程序,虽然缺乏正式的API,但确实通过服务器端代码(类/文件名等)给出了提示。有了这些,我创建了一个顶级对象作为整个问题域的容器;然后,我在需要的地方添加名称空间:

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

受之前帖子的启发,我制作了一个Rakefile和供应商目录的副本,并与WysiHat (changelog提到的RTE)一起分发,并进行了一些修改,包括使用JSLint进行代码检查和使用YUI Compressor进行缩小。

这个想法是使用Sprockets(来自WysiHat)将多个JavaScripts合并到一个文件中,在发布之前用JSLint检查合并文件的语法,并用YUI Compressor缩小它。

先决条件

Java运行时 红宝石和耙子宝石 您应该知道如何将JAR放入Classpath

现在做的

下载Rhino并将JAR(“js.jar”)放到类路径中 下载YUI Compressor并将JAR (build/yuicompressor-xyz.jar)放到类路径中 下载WysiHat并将“vendor”目录复制到你的JavaScript项目的根目录 下载JSLint for Rhino,并将其放到“vendor”目录中

现在,在JavaScript项目的根目录中创建一个名为“Rakefile”的文件,并向其添加以下内容:

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检查——检查代码的语法(这是默认任务,因此只需输入Rake) rake minify——准备JS代码的缩小版本

关于源合并

使用Sprockets, JavaScript预处理器可以包括(或要求)其他JavaScript文件。使用以下语法来包含初始文件(名为“main.js”,但你可以在Rakefile中更改)中的其他脚本:

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

然后……

看看WysiHat提供的Rakefile来设置自动化单元测试。不错的东西:)

现在揭晓答案

这并没有很好地回答最初的问题。我知道,对此我很抱歉,但我把它贴在这里,因为我希望它能对其他人整理他们的混乱有所帮助。

我解决这个问题的方法是尽可能多地进行面向对象建模,并将实现分离到不同的文件中。那么处理程序应该尽可能短。List singleton的例子也是一个很好的例子。

和名称空间……它们可以被更深层次的物体结构所模仿。

if (typeof org === 'undefined') {
    var org = {};
}

if (!org.hasOwnProperty('example')) {
    org.example = {};
}

org.example.AnotherObject = function () {
    // constructor body
};

我不太喜欢模仿,但是如果您有很多想要移出全局作用域的对象,这可能会很有帮助。