google 闭包库中的命名空间问题
Namespace issue in google closure library
教程中提到here,模块提供的命名空间是:
goog.provide('tutorial.notepad.Note');
但我想知道为什么不这样做:
goog.provide('tutorial.notepad');
因为,根据下面提到的规则:
tutorial = tutorial || {};
tutorial.notepad = tutorial.notepad || {};
tutorial.notepad.Note = tutorial.notepad.Note || {};
如果我们刚刚提供:
goog.provide('tutorial.notepad');
那么,我们已经有:
tutorial = tutorial || {};
tutorial.notepad = tutorial.notepad || {};
我们可以添加 属性 Note
tutorial.notepad.Note = function() {};
因此,我的问题是:
为什么不直接声明 goog.provide('tutorial.notepad')
然后用它来包含顶级 Classes
,而是建议对每个 Class
使用 goog.provide('tutorial.notepad.Note')
这对我来说是多余的.
让 goog.provide('tutorial.notepad');
在 "tree of dependencies" 中为该命名空间创建一个条目,但它不会为 class tutorial.notepad.Note
创建一个条目。如果您像在示例代码中那样手动创建 tutorial.notepad.Note
,那么您不会激活闭包编译器机制以将 class tutorial.notepad.Note
包含到闭包编译器使用的命名空间依赖项树中。
原因是 goog.provide
被闭包编译器用来设置依赖关系树,用于确定要加载的名称空间以及加载顺序。
通过不使用 goog.provide
,而是用您显示的代码模拟其效果,编译器不会了解 class Note
以及它如何适应树名称空间和 classes 及其依赖项。
运行 基于闭包编译器的代码有两种方法:已编译和未编译。这些中的每一个都以不同的方式构建和使用名称空间依赖关系树:
UNCOMPILED 闭包编译器的一大优点是您可以 运行 所有代码都未编译。该过程中的一个必要步骤是使用 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
在 运行 时访问它,以加载特定 class 所需的任何其他文件。
- COMPILED 编译器(Java 程序)作为编译过程的一部分,执行与上述相同的事情。不同之处在于,生成的名称空间依赖树仅在编译期间用于确定定义 class 的顺序,确定需要什么等。编译完成后,名称空间依赖树将被丢弃.
参考文献:
https://github.com/google/closure-compiler/wiki/Managing-Dependencies
https://github.com/google/closure-compiler/wiki/Debugging-Uncompiled-Source-Code
回复您的评论:
Why not just declare goog.provide('tutorial.notepad')
and then use that to include top level Classes
, instead its recommended to use goog.provide('tutorial.notepad.Note')
for each Class
which feels redundant to me.
我认为这涉及闭包编译器的目标和设计问题。正如@Technetium 指出的那样,使用闭包编译器 "is extremely verbose" - 它需要用注释注释您的 Java 脚本代码,以说明每个方法(函数)的输入和输出类型以及每个 属性 对象 (class).
(我不是编译器专家,但是)我认为按照您的建议进行操作需要编译器 "understand" 您的代码并猜测您认为什么是 class,以及什么您认为是 class 的构造函数和方法或其他属性。这将是一个 比闭包编译器设计者到达的问题更难的问题 - 特别是因为 JavaScript 是一种 "loose" 语言,它允许你几乎任何你能想到的。
实际上我发现 goog.provide
一点也不麻烦。我通常只为每个文件定义一个 class。我发现更麻烦的是所有 goog.require
语句。我通常可以在一个文件中包含 20 或 30 个这样的文件,并且这个文件列表经常以类似的方式重复 class。我的代码中出现了 3870 次 goog.require
。
即使这样也可以,但更糟糕的是闭包编译器有一个 goog.scope
机制,可以让你使用更短的名字,就像我可以说 Vector
而不是 new myphysicslab.lab.util.Vector
。这很好,但问题是每个 class 你已经 goog.require
d 然后你必须在 goog.scope
中创建一个短变量,像这样:
var Vector = myphysicslab.lab.util.Vector;
无论如何,我的观点是:是的,闭包编译器需要比原始 JavaScript 更多的代码。但是 goog.provide
是这方面最少的问题。
还有一件事:用户@Technetium 说
The real reason to use it is to run your Google Closure code through the javascript-to-javascript Closure Compiler that removes dead/unused code while minimizing and obfuscating the pieces you do use.
虽然这是一个非常有用的功能,但使用闭包编译器还有另一个非常重要的原因:类型检查。如果您花时间为您的函数添加注释,那么编译器将 "have your back" 捕获错误。这对任何项目都有很大的帮助,但当您有多个开发人员在一个项目上工作时就变得至关重要,这也是 Google 开发闭包编译器的主要原因之一。
这里有几件事在起作用:
- 每个命名空间只能调用一次
goog.provide()
。
您目前可能在单个文件中定义了 "class",例如 Note.js
,现在 goog.provide('tutorial.notepad');
。但是,如果您添加另一个文件,比如 Tab.js
,其中包含 "class" tutorial.notepad.Tab
,当 Tab.js
也调用 goog.provide('tutorial.nodepad')
.
- 调用
goog.provide('tutorial.notepad')
不会告诉闭包编译器有关 "class" tutorial.notepad.Note
Google 原始库形式的闭包代码极其冗长。使用它的真正原因是 运行 您的 Google 闭包代码通过 javascript-to-javascript 闭包编译器删除 dead/unused 代码,同时最小化和混淆您 做 使用的作品。虽然您的示例在调试模式下工作,因为它没有利用闭包编译器,但一旦闭包编译器 运行 并尝试构建依赖关系图,它将无法找到 tutorial.notepad.Note
class 时某些东西试图通过 goog.requires('tutorial.notepad.Note')
引用它。如果您想了解有关此依赖关系图如何工作的更多信息,owler 的回答是一个很好的起点。
顺便说一句,请注意我在引号中使用 "class",而且是故意的。虽然 Google Closure 通过其 @constructor
注释在许多方面提供了面向对象编程的外观和感觉,并且通过 goog.provide/goog.require
语法粗略地模拟了 package/import
,但它仍然是 JavaScript 在一天结束时。
教程中提到here,模块提供的命名空间是:
goog.provide('tutorial.notepad.Note');
但我想知道为什么不这样做:
goog.provide('tutorial.notepad');
因为,根据下面提到的规则:
tutorial = tutorial || {};
tutorial.notepad = tutorial.notepad || {};
tutorial.notepad.Note = tutorial.notepad.Note || {};
如果我们刚刚提供:
goog.provide('tutorial.notepad');
那么,我们已经有:
tutorial = tutorial || {};
tutorial.notepad = tutorial.notepad || {};
我们可以添加 属性 Note
tutorial.notepad.Note = function() {};
因此,我的问题是:
为什么不直接声明 goog.provide('tutorial.notepad')
然后用它来包含顶级 Classes
,而是建议对每个 Class
使用 goog.provide('tutorial.notepad.Note')
这对我来说是多余的.
让 goog.provide('tutorial.notepad');
在 "tree of dependencies" 中为该命名空间创建一个条目,但它不会为 class tutorial.notepad.Note
创建一个条目。如果您像在示例代码中那样手动创建 tutorial.notepad.Note
,那么您不会激活闭包编译器机制以将 class tutorial.notepad.Note
包含到闭包编译器使用的命名空间依赖项树中。
原因是 goog.provide
被闭包编译器用来设置依赖关系树,用于确定要加载的名称空间以及加载顺序。
通过不使用 goog.provide
,而是用您显示的代码模拟其效果,编译器不会了解 class Note
以及它如何适应树名称空间和 classes 及其依赖项。
运行 基于闭包编译器的代码有两种方法:已编译和未编译。这些中的每一个都以不同的方式构建和使用名称空间依赖关系树:
UNCOMPILED 闭包编译器的一大优点是您可以 运行 所有代码都未编译。该过程中的一个必要步骤是使用
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
在 运行 时访问它,以加载特定 class 所需的任何其他文件。
- COMPILED 编译器(Java 程序)作为编译过程的一部分,执行与上述相同的事情。不同之处在于,生成的名称空间依赖树仅在编译期间用于确定定义 class 的顺序,确定需要什么等。编译完成后,名称空间依赖树将被丢弃.
参考文献:
https://github.com/google/closure-compiler/wiki/Managing-Dependencies
https://github.com/google/closure-compiler/wiki/Debugging-Uncompiled-Source-Code
回复您的评论:
Why not just declare
goog.provide('tutorial.notepad')
and then use that to include top levelClasses
, instead its recommended to usegoog.provide('tutorial.notepad.Note')
for eachClass
which feels redundant to me.
我认为这涉及闭包编译器的目标和设计问题。正如@Technetium 指出的那样,使用闭包编译器 "is extremely verbose" - 它需要用注释注释您的 Java 脚本代码,以说明每个方法(函数)的输入和输出类型以及每个 属性 对象 (class).
(我不是编译器专家,但是)我认为按照您的建议进行操作需要编译器 "understand" 您的代码并猜测您认为什么是 class,以及什么您认为是 class 的构造函数和方法或其他属性。这将是一个 比闭包编译器设计者到达的问题更难的问题 - 特别是因为 JavaScript 是一种 "loose" 语言,它允许你几乎任何你能想到的。
实际上我发现 goog.provide
一点也不麻烦。我通常只为每个文件定义一个 class。我发现更麻烦的是所有 goog.require
语句。我通常可以在一个文件中包含 20 或 30 个这样的文件,并且这个文件列表经常以类似的方式重复 class。我的代码中出现了 3870 次 goog.require
。
即使这样也可以,但更糟糕的是闭包编译器有一个 goog.scope
机制,可以让你使用更短的名字,就像我可以说 Vector
而不是 new myphysicslab.lab.util.Vector
。这很好,但问题是每个 class 你已经 goog.require
d 然后你必须在 goog.scope
中创建一个短变量,像这样:
var Vector = myphysicslab.lab.util.Vector;
无论如何,我的观点是:是的,闭包编译器需要比原始 JavaScript 更多的代码。但是 goog.provide
是这方面最少的问题。
还有一件事:用户@Technetium 说
The real reason to use it is to run your Google Closure code through the javascript-to-javascript Closure Compiler that removes dead/unused code while minimizing and obfuscating the pieces you do use.
虽然这是一个非常有用的功能,但使用闭包编译器还有另一个非常重要的原因:类型检查。如果您花时间为您的函数添加注释,那么编译器将 "have your back" 捕获错误。这对任何项目都有很大的帮助,但当您有多个开发人员在一个项目上工作时就变得至关重要,这也是 Google 开发闭包编译器的主要原因之一。
这里有几件事在起作用:
- 每个命名空间只能调用一次
goog.provide()
。
您目前可能在单个文件中定义了 "class",例如 Note.js
,现在 goog.provide('tutorial.notepad');
。但是,如果您添加另一个文件,比如 Tab.js
,其中包含 "class" tutorial.notepad.Tab
,当 Tab.js
也调用 goog.provide('tutorial.nodepad')
.
- 调用
goog.provide('tutorial.notepad')
不会告诉闭包编译器有关 "class"tutorial.notepad.Note
Google 原始库形式的闭包代码极其冗长。使用它的真正原因是 运行 您的 Google 闭包代码通过 javascript-to-javascript 闭包编译器删除 dead/unused 代码,同时最小化和混淆您 做 使用的作品。虽然您的示例在调试模式下工作,因为它没有利用闭包编译器,但一旦闭包编译器 运行 并尝试构建依赖关系图,它将无法找到 tutorial.notepad.Note
class 时某些东西试图通过 goog.requires('tutorial.notepad.Note')
引用它。如果您想了解有关此依赖关系图如何工作的更多信息,owler 的回答是一个很好的起点。
顺便说一句,请注意我在引号中使用 "class",而且是故意的。虽然 Google Closure 通过其 @constructor
注释在许多方面提供了面向对象编程的外观和感觉,并且通过 goog.provide/goog.require
语法粗略地模拟了 package/import
,但它仍然是 JavaScript 在一天结束时。