JsInterop完全手册:J2CL中Java与JavaScript互操作详解

JsInterop完全手册:J2CL中Java与JavaScript互操作详解

【免费下载链接】j2cl Java to Closure JavaScript transpiler 【免费下载链接】j2cl 项目地址: 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 【免费下载链接】j2cl 项目地址: https://gitcode.com/gh_mirrors/j2/j2cl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值