为Closure Compiler问题注释JavaScript

时间:2013-03-12 04:11:11

标签: javascript google-closure-compiler strong-typing

我在externs文件中有以下typedef:

/** @typedef ({eventNameArray: Array.<string>,eventArrayIndex: number}) */
var triggerNextData;

希望它被用作triggerNext函数的传递参数(将触发下一个事件)。 eventNameArray(字符串数组)和eventArrayIndex都是必需的。

这是期望该类型的triggerNext函数:

/** 
* @type {function(triggerNextData)}
*/
triggerNext: function(data){
...
}

当我这样称呼时:

mediator.triggerNext("hi there");

我收到了预期的警告,但警告说eventNameArray是可选的:

  找到了:{eventArrayIndex:number,eventNameArray:   (Array。| null)} mediator.triggerNext(“hi there”);

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

以下内容未发出任何警告:

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

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

2 个答案:

答案 0 :(得分:3)

All objects can be null by default

  

默认情况下,所有对象类型都可以为Nullable,无论它们是否使用Nullable运算符声明。

您可以使用!表示它应该是非空值:

@typedef ({eventNameArray:!Array.<string>, eventArrayIndex:number})

至于数组的元素是字符串,我还不知道(还)。

答案 1 :(得分:2)

从closuretools.blogspot.co.uk/2012/02/type-checking-tips.html逐字复制,因为&#34;感谢您的建议。太糟糕的博客点在中国被封锁,所以无法读到。我看到很多关于这个的文章都在blogspot上,但是看不到它们&#34;

Closure Compiler的类型语言有点复杂。它有联合(“变量x可以是A或B”),结构函数(“变量x是一个返回数字的函数”)和记录类型(“变量x是具有属性foo和bar的任何对象”)。 / p>

很多人告诉我们,这仍然没有足够的表现力。有许多方法可以编写不适合我们的类型系统的JavaScript。人们建议我们应该添加mixins,traits和匿名对象的post-hoc命名。

这对我们来说并不特别令人惊讶。 JavaScript中的对象规则有点像Calvinball的规则。您可以随时更改任何内容,并制定新规则。很多人认为好的类型系统为您提供了一种强大的方式来描述您的程序的结构。但它也为您提供了一套规则。类型系统确保每个人都同意“类”是什么,“接口”是什么,以及“是”意味着什么。当您尝试向无类型JS添加类型注释时,您不可避免地会遇到一些问题,即我头脑中的规则与您头脑中的规则不完全匹配。没关系。

但我们感到惊讶的是,当我们给人们这种类型的系统时,他们经常找到多种方式来表达同样的事情。有些方法比其他方式更好。我以为我会写这篇文章来描述人们尝试的一些事情,以及它们是如何制定出来的。

功能与功能()

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

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

很多人问我们是否劝阻{Function},因为它不太具体。实际上,它非常有用。例如,考虑Function.prototype.bind的定义。它允许你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是一个对象。”你告诉它“桶被允许成为任何对象。”所以编译器必须假设任何人都可以将任何对象分配给“桶”,程序仍然是类型安全的。

相反,你通常最好使用@const。

/** @const */ var bucket = {};
/** @param {number} stuff */ bucket.fill = function(stuff) {};

现在我们知道存储桶不能分配给任何其他对象,编译器的类型推理引擎可以对存储桶及其方法进行更强大的检查。

一切都可以成为记录类型吗?

JavaScript的类型系统并不复杂。它有8种具有特殊语法的类型:null,undefined,boolean,number,string,Object,Array和Function。有些人注意到记录类型允许您定义“具有属性x,y和z的对象”,并且typedef允许您为任何类型表达式指定名称。因此,在两者之间,您应该能够使用记录类型和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) {}

这很有效。但是当你在程序的许多地方使用相同的记录类型时,事情会变得有些毛茸茸。假设您为makeBurger的参数创建了一个类型:

/** @typedef  {{withKetchup: (boolean|undefined),
                withLettuce: (boolean|undefined),
                withOnions: (boolean|undefined)}=} */
var BurgerToppings;

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

function makeBurgerForBob() {
  return makeBurger(bobsBurgerToppings);
}

后来,Alice在Bob的图书馆上建立了一个餐馆应用程序。在一个单独的文件中,她试图添加洋葱,但搞砸了API。

bobsBurgerToppings.withOnions = 3;

Closure Compiler会注意到bobsBurgerToppings不再匹配BurgerToppings记录类型。但它不会抱怨爱丽丝的代码。它会抱怨Bob的代码正在输入类型错误。对于非平凡的程序,Bob可能很难找出为什么这些类型不再匹配。

良好的类型系统不仅仅表达有关类型的合同。它还为我们提供了一种在代码违反合同时指定责任的好方法。因为类通常在一个地方定义,所以编译器可以确定谁负责破坏类的定义。但是当你有一个匿名对象被传递给许多不同的函数,并且从许多不同的地方设置了属性时,对于人类和编译器来说,要弄清楚谁违反了类型合同要困难得多。

由软件工程师Nick Santos发表