【问题标题】:Why doesn't Closure Compiler recognize type declarations inside a self-executing anonymous function?为什么 Closure Compiler 不能识别自执行匿名函数中的类型声明?
【发布时间】:2011-07-07 18:13:56
【问题描述】:

当我通过 Closure Compiler 运行一个相当大的库时,我收到了很多 "Unknown type" 警告,它们似乎发生在我的类型在自执行匿名函数中声明时。这并没有什么特别之处,但如果我去掉自执行函数,类型声明似乎可以工作(至少在这个简单的测试中)。

我不确定我的代码注释是否有问题,或者代码中是否有任何非法内容,但我认为这完全符合犹太教标准,也是模块化 API 的标准方法。

以下测试代码创建了一个命名空间(只是一个普通的旧 JS 对象)并附加了一个枚举(一个对象字面量)和一个函数。

var mynamespace = {};
(function (mynamespace) {
    /**
     * Some enum.
     * @enum {number}
     */
    mynamespace.SomeEnum = {
        FOO: 1,
        BAR: 2
    };

    /**
     * Frazzle some type.
     * @param {mynamespace.SomeEnum} qux The type to frazzle.
     * @return {boolean} whether the operation succeeded.
     */
    mynamespace.frazzle = function(qux) {
        return true;
    }
}(mynamespace));

// call it
mynamespace.frazzle(mynamespace.SomeEnum.FOO);

看起来不错,对吧?闭包编译器错误:

[jscomp] Compiling 1 file(s) with 37 extern(s)
[jscomp] X:\dev\solclientjs\sdk\tools\jscomptest.js:14: WARNING - Parse error. Unknown type mynamespace.SomeEnum

[jscomp]      * @param {mynamespace.SomeEnum} qux The type to frazzle.

【问题讨论】:

  • 在使用编译器一段时间后,我无法让它识别任何自定义数据类型。你? (至少这只是一个警告而不是真正的error =)
  • 我确定这只是一个错字,但为了正确起见,您的匿名函数周围的右括号应该在传递 mynamespace 之前放置:) (function (mynamespace) { ... })(mynamespace)跨度>
  • @john_doe:无论哪种方式都可以。 Here's an example. 这是 StackOverflow 上的 a related question

标签: javascript design-patterns google-closure-compiler jsdoc


【解决方案1】:

编辑:

原来的答案是完全错误的。

这肯定是编译器中的一个错误。我还没有找到关于这个确切问题的错误报告,但我发现了两个错误报告似乎解决了这个问题的反面(编译器应该发出警告,但除非你解开匿名函数,否则它不会)。

http://code.google.com/p/closure-compiler/issues/detail?id=134

http://code.google.com/p/closure-compiler/issues/detail?id=61

无论如何,匿名函数在与类型表达式一起使用时看起来很不稳定。

【讨论】:

  • 我认为这与 cmets 无关。 OP 正在使用由编译器解释的注释,正如他所说,它在立即调用的函数之外工作。当我测试 OP 的代码时,它使用 {number} 作为类型和 according to the docs 工作正常,你应该能够使用 type expression{mynamespace.SomeEnum}@param 标签。
  • 哈哈,是的,我认为这只是 JSDoc 语法(它就是这样)。那么这条线不应该只是* @param {number} qux The type to frazzle.吗?因为{number} 是类型表达式,而{mynamespace.SomeEnum} 不是。
  • 我还不太熟悉 Closure Compiler,但文档似乎确实表明您可以指定自定义类型。他们在文档中给出的例子是{goog.ui.Menu},所以看起来 OP 的使用是正确的。如果您从函数中解开代码,您也不会收到错误消息。似乎是一个错误,但我不确定。
  • 是的,我也不太熟悉,但经过一番挖掘,我认为这肯定是一个错误。答案已更新。
  • 这不是编译器中的错误。代码只是试图做一些 Compiler 无法处理的事情——即将一个对象作为参数传递给一个函数,扩充该参数对象,并期望扩充传播到外部。编译器将需要进行大量的代码流分析,以便找出在运行时函数内中为哪些对象分配了哪些属性。编译器只处理具有直接分配属性的类型,从不在函数内处理。这是使用编译器时的限制。
【解决方案2】:

在您的示例中,参数“mynamespace”被传递给 global mynamespace 对象的“别名”。为全局对象 (global mynamespace) 设置别名 (local mynamespace) 可防止对全局对象下的整个树进行优化。这对于高级模式下的 Closure Compiler 来说是个坏主意。

在局部变量下定义的任何函数都是易失的。编译器不知道(没有深入的代码流分析)本地“mynamespace”是全局“mynamespace”的别名。因此,它不会自动将您在局部变量下定义的任何内容与其别名的对象相关联。

正确的做法是:

选项#1,直接使用全局对象:

(function() {
    mynamespace.someEnum = ...
})();

选项 #2,使用 goog.scope 别名(假设您使用的是高级模式):

goog.scope(function() {
    var somevar = mynamespace;

    (function() {
        somevar.someEnum = ...
    })();
});

这两个选项都应该能得到你想要的结果。但是,目前没有方法可以通过将参数放入包装闭包中来执行您想要的操作。

【讨论】:

  • 我倾向于选项 #2。
  • @pope,最近版本的编译器在这方面往往会变得更好,所以你应该再次检查,因为我的 cmets(写于 2011 年 3 月)可能不再有效。
  • 还有另一个选项#3:使用闭包的 --output-wrapper 标志。
  • @huyz,是的,但是你只能包装整个东西,而不是任意的代码部分。
【解决方案3】:

闭包编译器在这里得到了改进。要更正上面的代码以使其被识别,您唯一需要做的就是将命名空间声明为@const:

/** @const */ var mynamespace = {};

然后识别类型。

【讨论】:

  • 像魅力一样工作。这是在哪里记录的?
  • 最近很多情况变得更好,因为编译器现在在类型检查时使用“推断常量”。一般来说,类型推断和检查的具体细节没有得到很好的记录。当新类型推断成为默认类型时,我希望看到这一点得到改进(目前正在从头开始重写)。
【解决方案4】:

闭包编译器不支持本地类型名称。名称必须是全局名称,并且在函数“mynamespace”中是本地名称。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-07
    • 2011-06-03
    • 2015-09-11
    • 1970-01-01
    • 2018-03-01
    • 2012-02-13
    • 1970-01-01
    相关资源
    最近更新 更多