注释闭包编译器问题的JavaScript

注释闭包编译器问题的JavaScript,javascript,google-closure-compiler,strong-typing,Javascript,Google Closure Compiler,Strong Typing,我在externs文件中有以下typedef: /** @typedef ({eventNameArray: Array.<string>,eventArrayIndex: number}) */ var triggerNextData; 当我这样称呼它时: mediator.triggerNext("hi there"); 我收到了预期的警告,但警告说eventNameArray是可选的: 找到:所需字符串:{eventArrayIndex:number,eventNameAr

我在externs文件中有以下typedef:

/** @typedef ({eventNameArray: Array.<string>,eventArrayIndex: number}) */
var triggerNextData;
当我这样称呼它时:

mediator.triggerNext("hi there");
我收到了预期的警告,但警告说eventNameArray是可选的:

找到:所需字符串:{eventArrayIndex:number,eventNameArray: (Array.| null)}mediator.triggerNext(“你好”)

不知何故,它没有拾取,它需要一个字符串数组(未显示类型数组),并且该数组是可选的

以下情况不会产生任何警告:

mediator.triggerNext({eventNameArray:null,eventArrayIndex:0});
mediator.triggerNext({eventNameArray:[22,33],eventArrayIndex:0});

我想知道如何将eventNameArray键入为字符串的强制数组,可以这样做吗?如果是这样的话,我该怎么做?

从closuretools.blogspot.co.uk/2012/02/type-checking-tips.html逐字复制,因为“谢谢你的建议。太糟糕了,blogspot在中国被屏蔽了,所以无法阅读。我在blogspot上看到了很多关于这方面的文章,但看不到。”

闭包编译器的类型语言有点复杂。它有并集(“变量x可以是A或B”)、结构函数(“变量x是返回数字的函数”)和记录类型(“变量x是具有foo和bar属性的任何对象”)

很多人告诉我们,这还不够表达。有许多方法可以编写不完全适合我们类型系统的JavaScript。人们建议我们应该添加mixin、traits和匿名对象的后期命名

这对我们来说并不特别奇怪。JavaScript中对象的规则有点像Calvinball的规则。你可以改变任何事情,并制定新的规则。很多人认为,一个好的类型系统为您提供了一个描述程序结构的强大方法。但它也给了你一套规则。类型系统确保每个人都同意什么是“类”,什么是“接口”,什么是“是”的意思。当您试图向非类型化的JS添加类型注释时,不可避免地会遇到我头脑中的规则与您头脑中的规则不太匹配的问题。没关系

但我们很惊讶,当我们给人们这种类型的系统,他们往往发现多种方式来表达同一件事。有些方法比其他方法更有效。我想我写这篇文章是为了描述人们尝试过的一些事情,以及他们是如何解决的

函数vs.函数()

描述函数有两种方法。一种是使用{Function}类型,编译器将其字面解释为“任何对象x,其中'x instanceof Function'为true”。
{Function}
故意弄脏了。它可以接受任何参数,并返回任何内容。你甚至可以在上面使用“new”。编译器将允许您随意调用它,而不会发出警告

结构函数更为具体,可以对函数的功能进行细粒度控制。
{function()}
不接受任何参数,但我们不关心它返回什么。
{function(?):number}
返回一个数字并只接受一个参数,但我们不关心该参数的类型。
{function(new:Array)}
在用“new”调用时创建一个数组。我们的类型文档和JavaScript样式指南中有更多关于如何使用结构函数的示例

很多人问我们是否不鼓励使用
{Function}
,因为它不太具体。实际上,它非常有用。例如,考虑<代码>函数。原型。绑定< /代码>。它允许你使用curry函数:你可以给它一个函数和一个参数列表,它会给你一个新函数,其中的参数是“预先填充的”。我们的类型系统不可能表示返回的函数类型是第一个参数类型的转换。因此,
Function.prototype.bind
上的JSDoc说它返回一个
{Function}
,编译器必须有手工编码的逻辑才能算出真正的类型

还有许多情况下,您希望传递回调函数来收集结果,但结果是特定于上下文的

rpc.get(‘MyObject’, function(x) {
  // process MyObject
});
如果您传递的回调参数必须对其得到的任何内容进行类型转换,那么“rpc.get”方法就要笨拙得多。因此,只给参数一个{Function}类型,并相信调用方类型不值得进行类型检查,通常会更容易

对象与匿名对象

许多JS库使用许多方法定义单个全局对象。该对象应该具有什么类型的注释

var bucket = {};
/** @param {number} stuff */ bucket.fill = function(stuff) {};
如果您来自Java,您可能会尝试只给它类型{Object}

/** @type {Object} */ var bucket = {};
/** @param {number} stuff */ bucket.fill = function(stuff) {};
这通常不是你想要的。如果你添加了“@type{Object}”注释,你不仅仅告诉编译器“bucket是一个对象”。你告诉它“bucket可以是任何对象”。因此编译器必须假设任何人都可以将任何对象分配给“bucket”,程序仍然是类型安全的

相反,使用@const通常会更好

/** @const */ var bucket = {};
/** @param {number} stuff */ bucket.fill = function(stuff) {};
现在我们知道bucket不能分配给任何其他对象,编译器的类型推理引擎可以对bucket及其方法进行更强有力的检查

一切都是记录类型吗

JavaScript的类型系统并没有那么复杂。它有8种特殊语法类型:null、未定义、boolean、number、string、Object、Array和Function。有些人注意到,记录类型允许您定义“具有属性x、y和z的对象”,typedefs允许您为任何类型表达式命名。因此,在这两者之间,您应该能够使用记录类型和typedef定义任何用户定义的类型。这就是我们所需要的吗

当您需要一个函数来接受大量可选参数时,记录类型非常有用。如果你有这个功能:

/**
 * @param {boolean=} withKetchup
 * @param {boolean=} withLettuce
 * @param {boolean=} withOnions
 */
function makeBurger(withKetchup, withLettuce, withOnions) {}
您可以使调用变得更容易,如下所示:

/**
 * @param {{withKetchup: (boolean|undefined),
            withLettuce: (boolean|undefined),
            withOnions: (boolean|undefined)}=} options
 */
function makeBurger(options) {}
这很有效。但是当你使用相同的rec
/**
 * @param {{withKetchup: (boolean|undefined),
            withLettuce: (boolean|undefined),
            withOnions: (boolean|undefined)}=} options
 */
function makeBurger(options) {}
/** @typedef  {{withKetchup: (boolean|undefined),
                withLettuce: (boolean|undefined),
                withOnions: (boolean|undefined)}=} */
var BurgerToppings;

/** @const */
var bobsBurgerToppings = {withKetchup: true};

function makeBurgerForBob() {
  return makeBurger(bobsBurgerToppings);
}
bobsBurgerToppings.withOnions = 3;
@typedef ({eventNameArray:!Array.<string>, eventArrayIndex:number})