在 Closure 中检测接口和鸭子类型

Detecting interfaces and duck typing in Closure

Closure 库中有几个地方的接口有一对 addImplementation/isImplementedBy 函数来对接口进行运行时类型检查(类似于 )。我不完全喜欢这个解决方案,因为我有一些非常简单的东西。有没有办法在启用 ADVANCED_OPTIMIZATIONS 的情况下进行鸭子打字?假设我有一个界面,以及一个对具有该界面的子项采取特殊操作的组件,例如:

/** @interface */
MyInterface = function() {};

MyInterface.prototype.doSomething = function() {};

/**
 * @constructor
 * @extends {goog.ui.Component}
 */
MyComponent = function() {
   ...
};

/** @inheritDoc */
MyComponent.prototype.addChild = function(child, opt_render) {
  goog.base(this, 'addChild', child, opt_render);
  if (child.doSomething) {
    child.doSomething();  
  }
};

ADVANCED_OPTIMIZATIONS 是否会始终如一地使用实现重命名 "doSomething" 属性?如果不是,添加类型联合会确保它会吗?例如

/**
 * @param {goog.ui.Component|MyInterface} child
 */
MyComponent.prototype.addChild = function( child, opt_render) {
  if (child.doSomething) {
    child.doSomething();  
  }
};

这就是添加 @record 的目的。您需要使用最新版本的编译器才能使用它(至少是 2016 年的版本)。

只需将 @interface 替换为 @record,您应该会得到您想要的行为。编译器会一致地重命名。

我将查德的回答作为解决方案,但在我开始编译后重新审视了这个问题,为了后代,我发布了一个更详细的鸭子打字一般情况的答案。

您可以使用 ADVANCED 优化执行动态向下转换,如下所示:

var maybeRecordType = /** @type {MyRecordType} */ (someObject);
if ( maybeRecordType.propertyInQuestion ) {
  maybeRecordType.propertyInQuestion();
}

如果传递的两种可能类型之间的 属性 名称存在冲突,那么您显然会以这种方式失去编译时检查,但这始终是鸭子类型的风险。