Kotlin 中调用 JavaScript

    但是,JavaScript 是一种动态类型语言,这意味着它不会在编译期检测类型。可以通过类型在 Kotlin 中自由地与 JavaScript 交流。如果想要使用 Kotlin 类型系统的全部威力,可以为 JavaScript 库创建 Kotlin 编译器与周边工具可理解的外部声明。

    An experimental tool to automatically create Kotlin external declarations for npm dependencies which provide type definitions (TypeScript / d.ts) called Dukat is also available.

    你可以使用 函数将一些 JavaScript 代码嵌入到 Kotlin 代码中。 例如:

    因为 js 的参数是在编译期解析并且按原样翻译成 JavaScript 代码的,因此它必须是字符串常量。因此,以下代码是不正确的:

    1. fun jsTypeOf(o: Any): String {
    2. return js(getTypeof() + " o") // 此处报错
    3. }
    4. fun getTypeof() = "typeof"

    Note that invoking js() returns a result of type dynamic, which provides no type safety at compile time.

    external 修饰符

    要告诉 Kotlin 某个声明是用纯 JavaScript 编写的,你应该用 external 修饰符来标记它。 当编译器看到这样的声明时,它假定相应类、函数或属性的实现是由外部提供的(由开发人员或者通过 ),因此不会尝试从声明中生成任何 JavaScript 代码。This is also why external declarations can’t have a body。例如:

    1. external fun alert(message: Any?): Unit
    2. external class Node {
    3. val firstChild: Node
    4. fun append(child: Node): Node
    5. fun removeChild(child: Node): Node
    6. // 等等
    7. }
    8. external val window: Window

    external 修饰符只允许在包级声明中使用。 你不能声明一个非 external 类的 external 成员。

    在 JavaScript 中,你可以在原型或者类本身上定义成员:

    Kotlin 中没有这样的语法。然而,在 Kotlin 中我们有伴生(companion)对象。Kotlin 以特殊的方式处理 external 类的伴生对象:替代期待一个对象的是,它假定伴生对象的成员就是该类自身的成员。可以这样描述来自上例中的 :

    1. external class MyClass {
    2. companion object {
    3. fun sharedMember()
    4. fun ownMember()
    5. }

    If you are writing an external declaration for a JavaScript function which has an optional parameter, use definedExternally. This delegates the generation of the default values to the JavaScript function itself:

    1. external fun myFunWithOptionalArgs(
    2. x: Int,
    3. y: String = definedExternally,
    4. z: String = definedExternally
    5. )

    With this external declaration, you can call myFunWithOptionalArgs with one required argument and two optional arguments, where the default values are calculated by the JavaScript implementation of myFunWithOptionalArgs.

    你可以轻松扩展 JavaScript 类,因为它们是 Kotlin 类。只需定义一个 external open 类并用非 external 类扩展它。例如:

    • 当一个外部基类的函数被签名重载时,不能在派生类中覆盖它。
    • 不能覆盖一个使用默认参数的函数。
    • 不能用外部类扩展非外部类。

    JavaScript 没有接口的概念。当函数期望其参数支持 foobar 两个方法时,只需传入实际具有这些方法的对象。

    在静态类型的 Kotlin 中,你可以使用接口来表达这一概念:

    1. external interface HasFooAndBar {
    2. fun foo()
    3. fun bar()
    4. }
    5. external fun myFunction(p: HasFooAndBar)

    外部接口的典型使用场景是描述设置对象。例如:

    1. external interface JQueryAjaxSettings {
    2. var async: Boolean
    3. var cache: Boolean
    4. var complete: (JQueryXHR, String) -> Unit
    5. }
    6. fun JQueryAjaxSettings(): JQueryAjaxSettings = js("{}")
    7. external class JQuery {
    8. companion object {
    9. fun get(settings: JQueryAjaxSettings): JQueryXHR
    10. }
    11. }
    12. fun sendQuery() {
    13. JQuery.get(JQueryAjaxSettings().apply {
    14. complete = { (xhr, data) ->
    15. window.alert("Request complete")
    16. }
    17. })
    18. }

    外部接口有一些限制:

    • 它们不能在 is 检测的右侧使用。
    • 它们不能作为具体化类型参数传递。
    • 它们不能用在类的字面值表达式(例如 I::class)中。
    • as 转换为外部接口总是成功。 Casting to external interfaces produces the “Unchecked cast to external interface” compile time warning. The warning can be suppressed with the @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") annotation.

      IntelliJ IDEA can also automatically generate the @Suppress annotation. Open the intentions menu via the light bulb icon or Alt-Enter, and click the small arrow next to the “Unchecked cast to external interface” inspection. Here, you can select the suppression scope, and your IDE will add the annotation to your file accordingly.

    They will be compiled accordingly:

    1. function usingUnsafeCast(s) {
    2. return s;
    3. }
    4. function usingAsOperator(s) {
    5. var tmp$;
    6. return typeof (tmp$ = s) === 'string' ? tmp$ : throwCCE();
    7. }