Js.cast() 如何执行类型检查?

How does Js.cast() perform its type checking?

我将 GWT 2.9 与 elemental2-1.0.0-RC1 一起使用。

以下代码在运行时抛出 ClassCastException

DocumentRange documentRange = Js.cast(DomGlobal.document); // Fails
Range range = documentRange.createRange(); // Never reaches here

当我改为使用 Js.uncheckedCast() 时,它成功了:

DocumentRange documentRange = Js.uncheckedCast(DomGlobal.document);
Range range = documentRange.createRange(); // Works

Js.uncheckedCast() 的文档说:

"You should always prefer regular casting over this (unless you know what you are doing!)."

我不知道为什么我必须使用它,所以我感到紧张。有人可以解释 Js.cast() 如何执行类型检查以及为什么我需要在这种情况下使用 Js.uncheckedCast() 吗?

Js.cast() 是一种作弊的方式,可以做一些 Java 语言不允许但实际上可能合法的事情。忽略 "how it actually works",我们的想法是您现在可以解决 Java 会抱怨的问题,即使它被证明是合法的。

一个例子可能是您使用 java.lang.Doubledouble 并希望将其视为 JsNumber 以便您可以对其调用 toPrecision(2)。由于 java.lang.Double 是最终的,转换为不相关的类型是不合法的,但是 Java 不知道在 GWT 中,Double 实际上只是一个 js Number。因此,您可以使用 Js.cast() 执行转换。编译器会在其中插入一个运行时类型检查,在运行时验证你的号码实际上是一个 JS Number 实例。

另一个示例可能是尝试扩展 elemental2 提供的某些本机类型,以实现缺失功能的解决方法,或执行特定于浏览器的操作。您的新 class 可能不会扩展现有的 class - 从 JS 的角度来看这没关系,您只是在描述您知道将在运行时存在的 API 。因此,我们需要避免 "does this cast even make sense?" 的 Java 语言检查,只告诉编译器尝试一下。

另一方面,您可以 "lie" 使用 Js.uncheckedCast() 编译器。这用于您甚至要求运行时跳过检查并假装它会工作的情况。这可以让你做一些奇怪的事情,比如把字符串当作数组来处理,或者解决跨框架问题。不会发出运行时检查,因此如果缺少 method/property,您可能只会得到 TypeError,而不是正确的 ClassCastException。


在 elemental2-dom 1.0.0-RC1 中,有一个名为 DocumentRange 的 class,但它实际上没有任何意义 - 它被声明为 [=63] =],这意味着它可以在 JS 中进行类型检查,但浏览器规范说它应该是一个 "interface"(在 JS 领域意味着它只是一种类型的描述,而不是你可以类型检查)。 https://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level2-DocumentRange-method-createRange

这个bug继承自闭包编译器,它声称它有一个构造函数:https://github.com/google/closure-compiler/blob/6a418aa/externs/browser/w3c_range.js#L241-L251

此修复是为了让闭包编译器将其称为接口,并制作新版本的 elemental2,以便您可以使用它。


您可以在此处进行两种解决方法。第一种是用Js.uncheckedCast(DomGlobal.document)作弊,说"yes, I know that the Document is not instanceof DocumentRange, but that's because there is no such class as DocumentRange, so just pretend it worked so I can call createRange() on it"。这就是您已经在做的事情 - 它隐藏了存在错误的事实,但最终它起作用了。

"correct" 答案是声明您自己的 DocumentRange,然后对其执行 Js.cast()。这仍然很糟糕 - 你必须保留你的新界面直到闭包得到修复,然后 elemental2 被释放,然后你必须记得清理它。

在这种情况下,我建议对 GWT 撒谎并使用 Js.uncheckedCast() - 这里只有一种方法,不太可能以有意义的方式进行更改。