拥有goog.provide('tutorial.notepad'); 会在该命名空间的“依赖树”中创建一个条目,但不会为tutorial.notepad.Note 类创建一个条目。如果您在示例代码中手动创建 tutorial.notepad.Note,那么您不会激活闭包编译器机制以将类 tutorial.notepad.Note 包含到闭包编译器使用的命名空间依赖关系树中。
原因是goog.provide 被闭包编译器用来建立依赖树,用于确定要加载的命名空间和顺序。
通过不使用goog.provide,而是用您显示的代码模仿其效果,编译器不会了解Note 类以及它如何适应命名空间和类及其依赖关系的树。
有两种方法可以运行基于闭包编译器的代码:已编译和未编译。它们中的每一个都以不同的方式构建和使用命名空间依赖树:
-
未编译 闭包编译器的一大优点是您可以运行所有未编译的代码。在这个过程中一个必要的步骤是使用depswriter.py,这是一个 Python 程序,它读取你所有的源文件(寻找goog.provide 和goog.require 调用)并生成一个文件deps.js。 deps.js 文件是命名空间依赖树的体现。这是我项目的deps.js 文件中的一个示例行(共 333 行):
goog.addDependency('../../../src/lab/app/ViewPanner.js',
['myphysicslab.lab.app.ViewPanner'], ['myphysicslab.lab.util.DoubleRect',
'myphysicslab.lab.util.UtilityCore', 'myphysicslab.lab.util.Vector',
'myphysicslab.lab.view.CoordMap', 'myphysicslab.lab.view.LabView'], false);
当我在未编译状态下运行我的代码时,有一个 <script> 标记运行该 deps.js 脚本。这样做会导致创建命名空间依赖树的内存版本,goog.require 在运行时访问它以加载该特定类所需的任何其他文件。
-
COMPILED 编译器(Java 程序)在编译过程中执行与上述相同的操作。不同之处在于,生成的命名空间依赖树仅在编译期间用于确定定义类的顺序,确定需要什么等。编译完成后,命名空间依赖树将被丢弃。
参考资料:
https://github.com/google/closure-compiler/wiki/Managing-Dependencies
https://github.com/google/closure-compiler/wiki/Debugging-Uncompiled-Source-Code
回复您的评论:
为什么不直接声明goog.provide('tutorial.notepad'),然后使用它来包含顶级Classes,而是建议为每个Class 使用goog.provide('tutorial.notepad.Note'),这对我来说是多余的。
我认为这涉及到闭包编译器的目标和设计问题。正如@Technetium 指出的那样,使用闭包编译器“非常冗长” - 它需要用 cmets 注释您的 JavaScript 代码,以告知每个方法(函数)的输入和输出类型以及对象(类)的每个属性的类型)。
(我不是编译器专家,但是)我认为按照您的建议进行操作需要编译器“理解”您的代码并猜测您认为什么是类,以及您认为什么是构造函数和方法或该类的其他属性。这将是一个比闭包编译器设计者解决的问题远困难 - 特别是因为 JavaScript 是一种如此“松散”的语言,它允许你做几乎任何你能想到的事情。
在实践中,我发现goog.provide 一点也不麻烦。我通常只为每个文件定义一个类。我发现更麻烦的是所有goog.require 语句。我通常可以在一个文件中包含 20 或 30 个这些文件,并且这个文件列表经常在类似的类中重复。我的代码中有 3870 次 goog.require。
即使这样也可以,但更糟糕的是闭包编译器有一个goog.scope 机制,可以让你使用更短的名称,就像我可以说Vector 而不是new myphysicslab.lab.util.Vector。这很好,但问题是你已经 goog.required 的每个类你都必须在 goog.scope 中创建一个短变量,如下所示:
var Vector = myphysicslab.lab.util.Vector;
无论如何,我的观点是:是的,闭包编译器需要比原始 JavaScript 更多的代码。但goog.provide 是这方面最少的问题。
还有一件事:用户@Technetium 声明
使用它的真正原因是通过 javascript-to-javascript Closure Compiler 运行您的 Google Closure 代码,该编译器可以删除死/未使用的代码,同时最小化和混淆您使用的部分。
虽然这是一个非常有用的功能,但使用闭包编译器还有另一个非常重要的原因:类型检查。如果您花时间将注释添加到您的函数中,那么编译器将通过捕获错误来“支持您”。这对任何项目都有很大帮助,但当您有多个开发人员在一个项目上工作时变得至关重要,这也是 Google 开发闭包编译器的主要原因之一。