JsInterop完全手册:J2CL中Java与JavaScript互操作详解
【免费下载链接】j2cl Java to Closure JavaScript transpiler 项目地址: https://gitcode.com/gh_mirrors/j2/j2cl
J2CL(Java to Closure JavaScript transpiler)是一个强大的工具,它允许开发者将Java代码转换为高效的JavaScript,同时提供了JsInterop机制实现Java与JavaScript之间的无缝互操作。本文将详细介绍JsInterop的核心概念、实用技巧和最佳实践,帮助开发者轻松掌握这一关键技术。
什么是JsInterop?
JsInterop是J2CL提供的一套注解系统,它允许Java代码与JavaScript代码进行双向通信。通过使用特定的注解,开发者可以控制Java类、方法和属性如何暴露给JavaScript,以及如何在Java中调用JavaScript API。这一机制极大地扩展了Java代码的能力,使其能够利用丰富的JavaScript生态系统。
JsInterop的核心优势
- 双向通信:既可以从Java调用JavaScript函数,也可以从JavaScript调用Java方法
- 类型安全:通过Java的类型系统提供编译时类型检查
- 无缝集成:与Closure编译器和JavaScript生态系统深度集成
- 低开销:生成高效的JavaScript代码,最小化性能损耗
核心注解详解
@JsType:JavaScript类型映射
@JsType是JsInterop中最基础也是最重要的注解,用于将Java类映射到JavaScript类型。它可以应用于类、接口和枚举。
@JsType(isNative=true, namespace="goog.html.sanitizer")
public class HtmlSanitizer {
public HtmlSanitizer(Builder builder){};
public native SafeHtml sanitize(String unsanitizedHtml);
@JsType(isNative=true)
public static class Builder {
public native Builder allowCssStyles();
}
}
关键属性:
isNative:标记是否为JavaScript原生类型namespace:指定JavaScript命名空间name:指定JavaScript中的类型名称
@JsMethod:暴露Java方法给JavaScript
@JsMethod用于将Java方法暴露给JavaScript,使其可以被直接调用。
class ClosureLinkify {
@JsMethod(namespace="goog.string.linkify")
public static native String findFirstEmail(String text);
@JsMethod(namespace="goog.string.linkify")
public static native String findFirstUrl(String text);
}
使用示例:
// Java代码
ClosureLinkify.findFirstEmail("this is an email: aaa@gmail.com");
// 转换后的JavaScript代码
const Linkify = goog.require("goog.string.linkify");
Linkify.findFirstEmail("this is an email: aaa@gmail.com");
@JsProperty:暴露Java属性给JavaScript
@JsProperty用于将Java字段或getter/setter方法暴露为JavaScript属性。
public class Interop {
@JsProperty
public static final int staticProp = 10;
@JsProperty
public int instanceProp = 30;
@JsProperty
public static Interop getInstance() {
return new Interop();
}
}
在JavaScript中使用:
console.log(Interop.staticProp); // 10
console.log(Interop.instance.instanceProp); // 30
@JsFunction:JavaScript回调函数
@JsFunction允许在Java中定义可以作为JavaScript回调的接口。
class Main {
@JsFunction
interface Callback {
void run();
}
@JsMethod(namespace=JsPackage.GLOBAL)
private static native int setTimeout(Callback callback, int delayMs);
public static void main() {
setTimeout(() -> {
// 延迟执行的代码
}, 1000);
}
}
@JsEnum:枚举类型映射
@JsEnum用于将Java枚举类型映射到JavaScript枚举。
@JsEnum(isNative=true)
enum ClosureEnum {
OK,
CANCEL
}
对于需要自定义值的枚举,可以使用hasCustomValue=true:
@JsEnum(hasCustomValue=true)
enum ExposedEnum {
VALUE1("Value1"),
VALUE2("Value2");
String value;
ExposedEnum(String value) {
this.value = value;
}
}
从Java调用JavaScript
调用全局JavaScript函数
通过@JsMethod注解并指定namespace=JsPackage.GLOBAL,可以调用全局JavaScript函数:
@JsMethod(namespace=JsPackage.GLOBAL)
private static native int setTimeout(Callback callback, int delayMs);
访问JavaScript对象和API
使用@JsType(isNative=true)可以定义JavaScript对象的Java接口:
@JsType(isNative=true, namespace=JsPackage.GLOBAL)
public class RegExp {
public RegExp(String pattern, String... flags) {}
public native boolean test(String str);
public String flags;
}
// 使用
RegExp re = new RegExp("asdf", "g");
boolean result = re.test("hello");
String flags = re.flags;
使用Closure库
J2CL与Closure库有深度集成,可以直接使用Closure的工具函数:
// JsInterop for goog.string.linkify
class ClosureLinkify {
@JsMethod(namespace="goog.string.linkify")
public static native String findFirstEmail(String text);
@JsMethod(namespace="goog.string.linkify")
public static native String findFirstUrl(String text);
}
从JavaScript调用Java
暴露Java类给JavaScript
使用@JsType注解的Java类会被转换为JavaScript类,可以直接实例化和使用:
@JsType
public class Exported {
public int prop;
public Exported() {
this.prop = 100;
}
public int value() {
return this.prop;
}
}
在JavaScript中使用:
const Exported = goog.require("com.google.example.Exported");
let e = new Exported();
console.log(e.prop); // 100
console.log(e.value()); // 100
构造函数暴露
使用@JsConstructor注解可以指定JavaScript可调用的构造函数:
class MyClass {
@JsConstructor
public MyClass() {}
}
在JavaScript中实例化:
const MyClass = goog.require("com.google.example.MyClass");
new MyClass();
方法和属性暴露
通过@JsMethod和@JsProperty注解,Java方法和属性可以直接在JavaScript中访问:
class Interop {
@JsMethod
public void jsMethod() {}
@JsProperty
public int instanceProp = 30;
}
高级技巧与最佳实践
处理可选参数
使用@JsOptional注解可以定义可选参数,与Closure编译器的可选参数机制对应:
@JsMethod
public void method1(@JsOptional @Nullable Double i) {}
转换后的JavaScript代码:
/**
* @param {number=} i // 注意这里的"="表示可选参数
* @return {void}
* @public
*/
method1(i) {}
处理JavaScript回调
使用@JsFunction定义回调接口,并在Java中实现:
@JsFunction
interface AsyncCallback {
void onSuccess(String result);
void onError(Error error);
}
@JsMethod(namespace="api")
public static native void fetchData(AsyncCallback callback);
// 使用
fetchData(new AsyncCallback() {
public void onSuccess(String result) {
// 处理成功结果
}
public void onError(Error error) {
// 处理错误
}
});
继承与方法重载
在处理继承关系和方法重载时,需要特别注意:
public class Main {
@JsMethod
public void method(@JsOptional @Nullable Object o) {
Console.log("Called method() with " + o);
}
@JsMethod
public native void method(); // 重载方法
}
命名空间处理
对于复杂的JavaScript命名空间,可以使用点表示法:
@JsType(isNative=true, name="bar.Foo", namespace="path.for.directory.of.type")
public class Foo {}
常见问题与解决方案
方法名冲突
当Java方法名与JavaScript关键字冲突时,可以使用name属性指定不同的JavaScript名称:
@JsMethod(name = "deleteItem")
public void delete() { ... }
处理JavaScript特殊类型
对于JavaScript特有的类型(如Promise),可以使用@JsType进行映射:
@JsType(isNative = true, name = "Promise", namespace = JsPackage.GLOBAL)
public interface Promise<T> {
@JsMethod
Promise<T> then(Thenable<T> onFulfilled);
@JsMethod
Promise<T> catch_(Thenable<Throwable> onRejected);
}
访问JavaScript命名空间属性
对于本身就是命名空间的属性,可以使用空字符串作为名称:
public class Main {
@JsProperty(namespace = "foo.bar", name = "")
public static native String bar();
}
总结
JsInterop为J2CL提供了强大的Java与JavaScript互操作能力,通过本文介绍的注解和技术,开发者可以轻松实现两种语言之间的无缝通信。无论是从Java调用JavaScript API,还是将Java代码暴露给JavaScript使用,JsInterop都提供了简洁而强大的解决方案。
要深入了解JsInterop的更多细节,可以参考官方文档:docs/jsinterop-by-example.md,其中包含了更多高级用法和示例。
通过掌握JsInterop,开发者可以充分利用Java的类型安全和JavaScript的生态系统优势,构建更加灵活和强大的Web应用。
【免费下载链接】j2cl Java to Closure JavaScript transpiler 项目地址: https://gitcode.com/gh_mirrors/j2/j2cl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



