Edit Page

在 JavaScript 中使用 Kotlin 代码

最终更新: 2024/03/21

根据选择的 JavaScript 模块 系统不同, Kotlin/JS 编译期会产生不同的输出. 但通常 Kotlin 编译器会生成通常的 JavaScript 类, 函数, 和属性, 你可以在 JavaScript 代码中自由地使用它们. 但是, 有一些细节问题, 你需要记住.

将声明隔离在 plain 模式下的独立 JavaScript 对象内

如果你将模块类型明确设置为 plain, Kotlin 会创建一个对象, 其中包含来自当前模块的所有 Kotlin 声明, 以免破坏全局对象. 因此, 对于模块 myModule, 在 JavaScript 中可以通过 myModule 对象访问到所有的声明. 比如:

fun foo() = "Hello"

在 JavaScript 中可以这样调用:

alert(myModule.foo());

如果你将你的 Kotlin 模块编译为 JavaScript 模块, 比如 UMD, CommonJS 或 AMD (对编译目标 browsernodejs, 默认设定是 UMD), 就会出现兼容问题. 这种情况下, 你的声明对外公开时使用的格式将由你选择的 JavaScript 模块系统决定. 比如, 如果使用 UMD 或 CommonJS, 那么需要这样来使用你的代码:

alert(require('myModule').foo());

关于 JavaScript 模块系统, 更多详情请参见 JavaScript 模块(Module).

包结构

Kotlin 会将它的包结构公开到 JavaScript 中, 因此, 除非你将你的声明定义在最顶层包中, 否则在 JavaScript 中就必须使用完整限定名来访问你的声明. 比如:

package my.qualified.packagename

fun foo() = "Hello"

比如, 如果使用 UMD 或 CommonJS, 那么调用端应该如下:

alert(require('myModule').my.qualified.packagename.foo())

如果模块系统使用 plain 模式, 那么应该是:

alert(myModule.my.qualified.packagename.foo());

@JsName 注解

某些情况下 (比如, 为了支持重载(overload)), Kotlin 编译器会对 JavaScript 代码中生成的函数和属性的名称进行混淆. 为了控制编译器生成的函数和属性名称, 你可以使用 @JsName 注解:

// 'kjs' 模块
class Person(val name: String) {
    fun hello() {
        println("Hello $name!")
    }

    @JsName("helloWithGreeting")
    fun hello(greeting: String) {
        println("$greeting $name!")
    }
}

然后, 你可以在 JavaScript 中通过以下方式来使用这个类:

// 如果需要, 请根据你选择的模块系统, import 对应的 'kjs' 模块
var person = new kjs.Person("Dmitry");   // 参照到 'kjs' 模块
person.hello();                          // 打印结果为 "Hello Dmitry!"
person.helloWithGreeting("Servus");      // 打印结果为 "Servus Dmitry!"

如果我们不指定 @JsName 注解, 那么编译器将会根据函数签名计算得到一个后缀字符串, 添加到生成的函数名末尾, 比如 hello_61zpoe$.

注意, 有些情况下 Kotlin 编译器不会进行这样的名称混淆:

  • external 声明, 不会进行名称混淆.
  • external 类继承的非 external 类之内, 被覆盖的函数, 不会进行名称混淆.

@JsName 注解的参数要求是字面值的字符串常量, 而且必须是一个有效的标识符. 如果将非标识符字符串用于 @JsName 注解, 编译器会报告错误. 下面的示例会常数一个编译期错误:

@JsName("new C()")   // 此处发生错误
external fun newC()

@JsExport 注解

@JsExport 注解目前还在实验性阶段. 在未来的发布版本中, 它的设计可能会发生变化.

对一个顶级声明 (比如一个类或函数)使用 @JsExport 注解, 就可以在 JavaScript 中访问 Kotlin 声明. 这个注解会使用 Kotlin 中给定的名称, 导出所有的下层声明. 使用 @file:JsExport 的方式, 还可以将这个注解应用于整个源代码文件.

为了在导出声明时解决名称的歧义(比如同名函数的重载(overload)), 可以将 @JsExport 注解和 @JsName 注解一起使用, 用来指定生成和导出的函数名称.

在当前默认的编译器后端, 以及新的 IR 编译器后端中, 都可以使用 @JsExport 注解. 如果你使用 IR 编译器后端, 那么 必须 从一开始就使用 @JsExport 注解, 来确保你的函数在 Kotlin 中可以使用.

对于跨平台项目, 在共通代码中也可以使用 @JsExport. 它只在针对 JavaScript 目标进行编译时才起作用, 并且允许你导出那些平台无关的 Kotlin 声明.

JavaScript 中的 Kotlin 类型

Kotlin 类型在 JavaScript 中映射为以下类型:

Kotlin 类型 JavaScript 类型 注释
Byte, Short, Int, Float, Double Number  
Char Number Number 表示字符的编码.
Long 不支持 JavaScript 中没有 64 位整数类型, 因此它使用一个 Kotlin 类来模拟.
Boolean Boolean  
String String  
Array Array  
ByteArray Int8Array  
ShortArray Int16Array  
IntArray Int32Array  
CharArray UInt16Array 包含属性 $type$ == "CharArray".
FloatArray Float32Array  
DoubleArray Float64Array  
LongArray Array<kotlin.Long> 包含属性 $type$ == "LongArray". 另外请参见 Kotlin 的 Long 类型的注释.
BooleanArray Int8Array 包含属性 $type$ == "BooleanArray".
Unit Undefined  
Any Object  
Throwable Error  
可为 Null 的 Type? Type \| null \| undefined  
Kotlin 的所有其他类型 (使用 JsExport 注解标注的类型除外) 不支持 包含 Kotlin 的集合(List, Set, Map, 等等.), 以及无符号类型(unsigned variant).

此外, 还要注意:

  • Kotlin 为 kotlin.Int, kotlin.Byte, kotlin.Short, kotlin.Charkotlin.Long 保留了溢出语义.
  • Kotlin 在运行时无法区分数值类型(除 kotlin.Long 外), 因此以下代码能够正常工作:

    fun f() {
        val x: Int = 23
        val y: Any = x
        println(y as Float)
    }
    
  • Kotlin 在 JavaScript 中保留了延迟加载对象的初始化处理.
  • Kotlin 在 JavaScript 中没有实现顶级属性的延迟加载初始化处理.