【问题标题】:How to solve the Google Closure namespace hell?如何解决 Google Closure 命名空间地狱?
【发布时间】:2014-01-14 05:41:34
【问题描述】:

我的新工作是使用 Google Closure 库编写面向组件的 JavaScript。我喜欢事件、组件、服务和模块。但这项工作非常艰巨,因为需要编写充满名称空间的代码。以下代码是典型的:

goog.provide(com.bin.slash.dot.closure.widget.SuperForm);

goog.require(com.bin.slash.dot.closure.widget.Avatar);
// ... ten require calls more...

com.bin.slash.dot.closure.widget.SuperForm = function() {
  goog.base(this);
  this._internal = new com.bin.slash.dot.closure.widget.Avatar(
    com.bin.slash.dot.closure.widget.Avatar.SRC_PATH);
};

我不敢相信这是真的。我不害怕输入所有这些,但我只是觉得逻辑在这个符号地狱中溶解和混乱。扫描非常困难,因此需要更多时间来了解发生了什么。 我的老板说,不鼓励写这样的快捷方式:

var SF = com.bin.slash.dot.closure.widget.SuperForm = function(){};

因为它们在编译后都会绑定到全局命名空间(窗口),所以它们可以干扰其他东西。

问题是如何避免这个符号地狱?

更新:我对我的开发人员流程进行了改进,解决了符号地狱问题。 现在我编写了经过优化的 JavaScript,然后由 Grunt 使用 sweet.js 宏自动编译:

// For each file I define three macros which are replaced 
// in the compile time with hell of a long paths.
macro dir { rule { $x } => { my.very.very.long.namespace $x } }
macro class { rule { $x } => { dir.NameOfMyClass $x } }
macro proto { rule { $x } => { class.prototype $x } }

dir.NameOfMyClass = function() {}; // yields: my.very.very.long.namespaceNameOfMyClass = function() {};
class.CONSTANT = "I don't know why we write constants into classes, not prototypes"; // yields: my.very.very.long.namespaceNameOfMyClass.CONSTANT = ...;
proto.method1 = function() {}; // yields my.very.very.long.namespaceNameOfMyClass.prototype.method1 = function(){};

所有由宏编译器产生的噪音都被优秀的shelljs去除了。

【问题讨论】:

  • 我不确定我在这里看到你的问题。
  • 只定义局部变量而不是全局变量?
  • 至于:“我不知道为什么我们将常量写入类,而不是原型”——这样做的一个原因是编译器可以更有效地将其重命名为 a 而不是 @987654325 @ 在高级模式下。可能还有更重要的原因。

标签: javascript google-closure-library


【解决方案1】:

假设您正在使用 Closure 编译器,请考虑 goog.scope。有内置的编译器支持,可以在优化之前替换别名变量:

概括地说

  • goog.providegoog.require 语法不变;
  • 您可以开始在作用域内声明非构造函数命名空间;
  • 构造函数命名空间需要在重命名它们的范围之外声明。

考虑到上述指南,您的代码示例可能看起来像这样:

goog.provide('my.very.long.namespace.NameOfMyClass');
goog.require('my.very.long.namespace');

/** @constructor */
my.very.long.namespace.NameOfMyClass = function() { /*...*/ };

goog.scope(function() {
  var _ = my.very.long.namespace.NameOfMyClass;

  _.CONSTANT = 'I don\'t know why we write constants into classes, not prototypes';

  _.prototype.method1 = function() {};

}); // goog.scope

额外

由于我没有足够的声誉来添加评论:

我的老板说,不鼓励写这样的快捷方式:

var SF = com.bin.slash.dot.closure.widget.SuperForm = function(){};

因为它们都将绑定到全局命名空间(窗口) 编译后,所以它们可以干扰其他东西。

即使没有这些快捷方式,高级编译也可以将SFmyVariable 等符号重命名为简单的ga。这可能会导致与外部代码(例如 Google Analytics)发生冲突。

在全局范围内防止此类冲突的闭包支持方法是在编译后引入立即调用的函数表达式 (source)。使用编译器标志:--output_wrapper "(function(){%output%})();",或严格模式兼容的变体:--output_wrapper "(function(){%output%}).call(this);"。使用时,老板不鼓励的快捷方式将不会与外部符号发生冲突。

【讨论】:

    【解决方案2】:

    定义引用以简化 IMO 工作流程并没有错。

    var 
    widget = com.bin.slash.dot.closure.widget
    widget.methA = function(){ widget.propertyA = 10}
    

    命名空间是有目的的,虽然我不能谈论你的代码库,但可能有更好的方法来组织这个库。

    【讨论】:

    • 当我们获得非典型的长命名空间时,我们在公司就是这样做的。如果您将命名空间定义为函数顶部附近的引用,它可以让眼睛在单个逻辑块中掩盖函数的命名空间和命名空间引用,让您专注于包含核心的函数的其余部分逻辑。
    • @Technetium 这确实是一个好方法,但是如果你不将它封装在某个本地范围内,你将覆盖 window 对象的 widget 属性,这可能会导致问题。
    • 请修复此代码,以免使用别名创建全局变量
    【解决方案3】:

    使用立即函数创建单独的本地上下文怎么样:

    (function () {
        var SF = com.bin.slash.dot.closure.widget.SuperForm = function(){};
    }());
    

    您的代码应该只放在这个函数中。这样您就永远不会覆盖任何全局变量。

    【讨论】:

    • 是的,安全包装器是个好主意,但它适合高级模式下的闭包编译器吗?今天将进行测试。
    • 我不认为它会摆脱即时功能,而是等待你的测试。
    • 我不想这么说,但我没有做测试,抱歉。但我找到了一个我更喜欢的解决方案,并将其发布到原始问题中。
    • 没关系 - 通常有不止一种解决方案,您可以选择最适合您的方案。此外,您应该将其作为答案发布并接受它,这样问题就不会一直悬而未决。
    • @overmind1 这样的包装函数确实很重要,可以在全局范围内隐藏别名。但是为了让 Closure Compiler 正确处理别名,必须将包装函数传递给 goog.scope()。见mesteiral's answer。另请参阅 this documentation in the Compiler source code 以获得良好的描述。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-15
    • 2015-12-20
    • 2011-01-31
    • 1970-01-01
    • 1970-01-01
    • 2017-08-26
    相关资源
    最近更新 更多