JavaScript对象依赖性

4
在复杂的客户端项目中,JavaScript文件的数量可能会非常大。但是,出于性能原因,将这些文件连接起来并压缩结果文件以通过网络发送是很好的做法。我在连接这些文件时遇到了问题,因为有些情况下需要在它们被引用之前先包含依赖项。
例如,有2个文件:
/modules/Module.js <requires Core.js>
/modules/core/Core.js

目录会进行递归遍历,在Core.js之前包含Module.js,导致出现错误。这只是一个简单的例子,依赖关系可能跨越目录,并且可能存在其他复杂情况。但没有循环依赖关系。
我遵循类似于Java包的Javascript结构,每个文件定义一个单独的对象(我使用MooTools,但这并不重要)。每个Javascript文件和依赖关系的结构始终保持一致: Module.js
var Module = new Class({
    Implements: Core,

    ...
});

Core.js

var Core = new Class({
    ...
});

在Javascript文件数量巨大且存在文件间依赖关系的项目中,您通常会采取哪些做法来处理依赖关系呢?

9个回答

2
使用目录是聪明的选择,但是当您有多个依赖项时可能会遇到问题。我发现我不得不创建自己的解决方案来处理这个问题。因此,我创建了一个值得一试的依赖管理工具。(Pyramid Dependency Manager documentation
它做了其他JavaScript依赖管理器不做的一些重要的事情,主要包括:
  1. 处理其他文件(包括在开发期间插入视图的HTML...是的,在开发期间可以分离视图)
  2. 在发布时为您合并文件的JavaScript(无需安装外部工具)
  3. 具有所有HTML页面的通用包含。当添加、删除、重命名依赖项时,只需要更新一个文件
以下是一些示例代码,展示它在开发期间的工作方式。
文件:dependencyLoader.js
//Set up file dependencies
Pyramid.newDependency({
    name: 'standard',
    files: [
    'standardResources/jquery.1.6.1.min.js'
    ]
});

Pyramid.newDependency({
name:'lookAndFeel',
files: [
    'styles.css',
    'customStyles.css'
    ]
});

Pyramid.newDependency({
name:'main',
files: [
    'createNamespace.js',
    'views/buttonView.view', //contains just html code for a jquery.tmpl template
    'models/person.js',
    'init.js'
    ],
    dependencies: ['standard','lookAndFeel']
});

HTML文件
<head>
    <script src="standardResources/pyramid-1.0.1.js"></script>
    <script src="dependencyLoader.js"></script>
    <script type="text/javascript">
        Pyramid.load('main');
    </script>
</head>

1
这可能有些粗糙,但我的做法是将我的不同脚本片段保存在不同的文件中。我的项目允许我将所有的Javascript都用于每个页面(因为毕竟它会被缓存,而且我没有注意到解析过程中的性能问题)。因此,在构建时,我的Ant脚本通过一个小型的自定义Ant任务运行Freemarker。该任务在源目录树中搜索并收集所有单独的Javascript源代码文件到一组Maps中。有几种不同类型的源(jQuery扩展、一些页面加载操作、一些通用的工具等),所以该任务将这些不同种类的源分组在一起(从脚本源目录结构中获取其提示)。

一旦构建了Maps,它就将这些内容输入到Freemarker中。使用Freemarker,有一个全局模板,通过它,所有的脚本片段都被打包成一个文件。然后,这个文件经过YUI压缩器的处理,完成了!每个页面只需获取这一个脚本,一旦被缓存,整个站点上就不再需要获取脚本了。

依赖关系?那个Ant任务会按名称对我的源文件进行排序,因此在需要确保定义-使用顺序的地方,我只需在文件名前加上数字代码。 (在某个时候,我会将其改进,以便源文件可以在注释块或其他地方中保留其排序信息,甚至明确声明依赖关系。但我并不是太有动力,因为尽管它有点丑陋,但它真的不会太影响任何人。)

我喜欢使用目录名称和文件扩展名将事物组合在一起的想法。有趣的是你提到了依赖关系,因为我也一直这样做(前缀下划线),直到今天当几个js文件添加到项目中后,事情开始变得非常烦人,导致它停止工作。 - Anurag

1

我写了一个非常简陋的依赖项查找器,基于它我正在进行串联。结果发现它使用MooTools并不是那么无关紧要。这个解决方案非常好,因为它不需要单独维护依赖信息,因为它在javascript文件本身中可用,这意味着我可以非常懒惰。

由于类和文件命名一致,类Something将始终具有文件名Something.js。为了查找外部依赖项,我正在寻找三件事:

  1. 它是否实现其他类
  2. 它是否扩展其他类
  3. 它是否使用new关键字实例化其他类
在每个 JavaScript 文件中搜索上述三种模式,以获取其依赖类。找到依赖类后,将搜索位于任何文件夹中的所有 JavaScript 文件,并与该类名匹配,以确定该类的定义位置。一旦找到依赖项,我会构建一个依赖图,并使用拓扑排序算法生成应包含文件的顺序。

0

你的目录结构被倒置了...

核心依赖应该在根目录下,而模块应该在子目录中。

scripts/core.js
scripts/modules/module1.js

你的问题就解决了。

任何进一步的依赖问题都说明了有缺陷的“类”/依赖设计。


那只是一个例子,也许我选择的名称不太合适。但问题基本上与依赖项有关,与核心组件无关。 - Anurag
@Anurag - 答案不会改变。如果您正在从递归目录扫描中加载脚本,请将依赖项放置在比依赖项更接近根的位置。简单明了。 - Sky Sanders
我们的结构类似于Java或其他语言中的包。如果包的结构设计良好,则没有任何限制应该靠近根目录。此外,同一个包内可能存在依赖关系(我们确实有这种情况),将文件重命名以在某个列表中首先出现只是一种欺骗方式(我们之前一直在这样做)。 - Anurag
@Anurag - 你说过“目录是递归遍历的”,在你的加载策略上下文中,这就是我看到的你目录结构的问题。而JS不是Java,没有内在的链接器。如果你想要那种能力,你已经知道了选项,但那不是你所描述的。只是我的0.2个比索。 - Sky Sanders
我在问题陈述中包含了我的当前解决方案,这并不总是最好的做法,特别是当我们的答案朝着错误的方向时。无论如何,我编写了一个粗糙、混乱的自动依赖项查找器,目前似乎能够胜任工作。 - Anurag

0

打破解析时间或加载时间依赖的一种方法是使用自定义对象(一种自定义函数的变体)。

假设你有这样一个东西:

var obj = new Obj();

这行代码位于someFile.js文件中,Obj在Obj.js中定义。为了成功解析,您必须在加载或连接someFile.js之前加载或连接Obj.js。

但是,如果您像这样定义obj:

var obj = {
   init: function() {
        obj = new Obj();
    }
};

然后在解析或加载时,无论您以什么顺序加载这两个文件,只要Obj在运行时可见即可。您将不得不调用obj.init()以使对象处于所需状态,但这是为打破依赖关系而付出的小代价。

为了更清楚地说明这是如何工作的,这里有一些代码可以剪切并粘贴到浏览器控制台中:

var Obj = function() {
    this.func1 = function ( ) {
        console.log("func1 in constructor function");
    };
    this.init = function () {
        console.log("init in constructor function");
    }
};

var obj = {
    init: function() {
        console.log("init in original object");
        obj = new Obj();
        obj.init();
    }
};

obj.init();
obj.func1();

你也可以尝试使用像RequireJS这样的模块加载器。


0

我建议您将这些文件按顺序复制粘贴到一个文件中。每个文件都需要有起始和结束注释来区分每段代码。

每次更新其中一个文件时,您都需要更新该文件。因此,该文件只需要包含不会在短时间内更改的“完成”库。


哈哈,我有100多个JS文件,而且这只是早期阶段.. 可以为此目的创建一个全职工作:P - Anurag
稍微修改一下你的解决方案,可以轻松地创建另一个名为 dependencies.list 的文件,其中列出了应按顺序包含的所有 js 文件(带完整路径)。现在,构建脚本可以创建一个 temp 文件,遍历 dependencies.list 中列出的每个文件的内容,并继续附加到 temp。这样只需要更改一个文件,就可以保持排序。即使只有两个人在处理此问题,这也会变得非常复杂。 - Anurag

0

与Mendy类似,但我在服务器端创建组合文件。创建的文件也将被压缩,并具有唯一的名称以避免更新后的缓存问题。

当然,这种做法只在整个应用程序或框架中才有意义。


你是否在某个地方定义了依赖项,以便服务器端脚本在连接时考虑它们? - Anurag
服务器端脚本定义了所需文件列表。并不只有一个 js 文件,而是类似于 array('media.js'=>array('a.js', 'b.js',...),'mmgt.js'=>...) 这样的形式。因此,单个服务器端脚本可能需要 media.js,另一个可能需要 media.js 和 mmgt.js 等等。 - Frunsi

0

如果可能的话,我认为你最好的选择是重新设计,不要有大量具有文件间依赖关系的JavaScript文件。JavaScript并不打算走这条路。


这是为一个RIA设计的,所有视图和逻辑都是使用JavaScript从头开始构建的。只有在需要数据时才会与远程服务通信。实际上,大多数文件非常短,可能只有6-10行,但这在管理复杂性方面非常有帮助。 - Anurag

0

Core Depender看起来非常不错,但我的当前项目是在Rails中,我不认为有一个适用于Dependent.Server的Rails组件。此外,只要我遵循某些约定,我的方法应该可以在不手动指定依赖项的情况下工作。 - Anurag
1.3很快就要发布了,会带来很多服务端JS的好东西,包括CommonJS支持、Rails相关内容等等。请耐心等待 :) - Dimitar Christoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接