Rust & WebAssembly:探索js-sys的奇妙世界

Rust & WebAssembly:探索js-sys的奇妙世界

一 js-sys 库简介

js-syswasm-bindgen 工具链中的一个底层核心库。它提供了对 JavaScript 全局对象和内置对象的裸绑定(raw bindings),即对 JavaScript 全局对象和内置类型的原生绑定。它不添加抽象层,让你在 Rust 中直接操作 JS 原生对象,就像在 JavaScript 中一样自然。

通过js-sys在 Rust 中创建和操作 JavaScript 的ObjectArrayFunction等对象,就像在 JavaScript 中操作它们一样自然。

js-sys库为 Rust 与 JavaScript 之间的交互提供了更底层、更直接的方式,是实现复杂 WebAssembly 应用的重要基础。

核心定位

  • 提供裸绑定(Raw Bindings)到 JavaScript 内置对象
  • 支持所有 ECMAScript 标准类型
  • 作为 wasm-bindgen 的底层基础设施

二 js-sys 中的常见类型使用

(一)Object 类型

在 Rust 中,通过js-sys库可以方便地创建和操作 JavaScript 中的Object对象。

比如,我们可以使用Object::new()函数来创建一个新的 JavaScript 对象,通过Reflect::set()方法来设置对象的属性,使用Reflect::get()方法获取对象的属性值 。示例代码如下:

rust 复制代码
use wasm_bindgen::prelude::*;
use js_sys::{Object, Reflect};

#[wasm_bindgen]
pub fn create_js_object() -> Result<Object, JsValue> {
    // 创建一个空的 JS 对象
    let obj = Object::new();

    // 使用 Reflect::set 来设置属性
    // Reflect.set(target, propertyKey, value)
    Reflect::set(&obj, &"message".into(), &"This object was born in Rust!".into())?;
    Reflect::set(&obj, &"id".into(), &99.into())?;

    // 创建一个嵌套对象
    let nested_obj = Object::new();
    Reflect::set(&nested_obj, &"status".into(), &"active".into())?;
    Reflect::set(&obj, &"data".into(), &nested_obj.into())?;

    Ok(obj)
}

在JavaScript 中调用:

javascript 复制代码
import init, { create_js_object } from './pkg/your_pkg_name.js';

const run = async () => {
    await init();
    const myObject = create_js_object();
    console.log("Object from Rust:", myObject);
    console.log("Message:", myObject.message); // "This object was born in Rust!"
    console.log("ID:", myObject.id);         // 99
    console.log("Nested Status:", myObject.data.status); // "active"
};

run();

(二)Array 类型

利用js-sys库,在 Rust 中创建和操作 JavaScript 的Array数组也很简单。可以使用Array::new()函数创建数组,通过Array::push()方法添加元素 ,Array::pop()方法删除元素,使用索引来访问数组元素。示例代码如下:

rust 复制代码
use wasm_bindgen::prelude::*;
use js_sys::Array;
use web_sys::console;

#[wasm_bindgen]
pub fn use_js_array() -> Array {
    // 创建一个 JavaScript 数组
    let arr = Array::new();

    // 添加不同类型数据
    arr.push(&"Rust".into());
    arr.push(&2023.into());
    arr.push(&true.into());
    arr.push(&JsValue::from_str("Hello from Rust!")); // 字符串
    arr.push(&JsValue::from_f64(123.45));           // 数字
    arr.push(&JsValue::from_bool(true));             // 布尔值

    // 遍历数组
    for i in 0..arr.length() {
        let value = arr.get(i);
        console::log_1(&format!("Element at index {}: {:?}", i, value).into());
    }
    arr
}

在JavaScript 中调用

javascript 复制代码
import init, { use_js_array } from './pkg/your_pkg_name.js';

const run = async () => {
    await init();
    const myArray = use_js_array();
    console.log("Array created in Rust:", myArray);
    console.log("Is it a real JS Array?", Array.isArray(myArray)); // true
    console.log("Content:", myArray[0], myArray[1], myArray[2]);
};

run();

(三)Function 类型

js-sys库中对函数类型的处理,使得我们可以在 Rust 中定义和调用 JavaScript 函数。可以使用Function::new()函数定义一个新的 JavaScript 函数,通过Function::call()方法调用函数。示例代码如下

rust 复制代码
use js_sys::{ Array, Function, Reflect };

#[wasm_bindgen]
pub fn call_js_callback(callback: &Function) {
    let args = Array::new();
    args.push(&"来自Rust的消息".into());
    Reflect::apply(callback, &JsValue::NULL, &args).unwrap();
}

#[wasm_bindgen]
pub fn create_rust_function() -> Function {
    Function::new_with_args("a, b", "return a + b")
}

#[wasm_bindgen]
pub fn create_function() -> Result<(), JsValue> {
    // 创建一个 JavaScript 函数
    let func = Function::new_no_args("
        console.log('Hello from JavaScript!');
    ");

    // 调用该函数
    func.call0(&JsValue::undefined())?;
    Ok(())
}

在JavaScript 中调用:

javascript 复制代码
import init, { call_js_callback, create_rust_function, create_function } from './pkg/your_pkg_name.js';

const run = async () => {
    await init();
    // 调用 Rust 创建的函数
    const addFn = create_rust_function();
    addFn(2, 3); // 返回 5

    // 传递回调给 Rust
    call_js_callback((msg) => alert(msg));
    create_function(); // Hello from JavaScript!
};

run();

(四)Date 类型

在处理日期和时间时,js-sys库提供了Date类型。可以使用Date::new()函数创建日期对象,通过Date::get_full_year()等方法获取日期信息。示例代码如下:

rust 复制代码
use js_sys::Date;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn format_timestamp(ts: f64) -> String {
    let date = Date::new(&JsValue::from(ts));
    format!(
        "{}-{:02}-{:02}",
        date.get_full_year(),
        date.get_month() + 1,
        date.get_date()
    )
}

#[wasm_bindgen]
pub fn get_js_timestamp() -> f64 {
    // 使用 js_sys::Date::new_0() 创建一个新的 Date 对象,等同于 JS 的 new Date()
    let date = Date::new_0();
    
    // 调用 getTime() 方法,返回从 1970-01-01 至今的毫秒数
    // 注意这里返回的是 f64
    date.get_time()
}

在JavaScript 中调用:

javascript 复制代码
import init, { format_timestamp, get_js_timestamp } from './pkg/your_pkg_name.js';

const run = async () => {
    await init();
    const ts = get_js_timestamp();
    console.log("Current timestamp:", ts); // 时间戳
    console.log("Formatted date:", format_timestamp(ts)); // 格式化后的日期字符串,如 2025-08-01
};

run();

(五)Error 类型

通过js-sys库,可以在 Rust 中处理 JavaScript 错误。可以使用Error::new()函数创建错误对象,在try {} catch {}块中捕获错误。示例代码如下:

rust 复制代码
use js_sys::Error;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn validate_input(input: &str) -> Result<(), JsValue> {
    if input.is_empty() {
        Err(Error::new("输入不能为空").into())
    } else {
        Ok(())
    }
}

#[wasm_bindgen]
pub fn error_msg() {
    // 创建一个错误对象
    let error = Error::new("Something went wrong!");

    // 获取错误信息
    let message = error.message();
    console::log_1(&format!("Error: {}", message).into());
}

在JavaScript 中调用:

javascript 复制代码
import init, { validate_input, error_msg } from './pkg/your_pkg_name.js';

const run = async () => {
    await init();
    try {
        validate_input("");
    } catch (error) {
        console.log("Caught error:", error); //Caught error:输入不能为空 ... 
        error_msg(); // Error: Something went wrong!
    }
};

run();

(六)Promise 类型

js-sys库中Promise类型的使用,让我们能在 Rust 中处理异步操作。可以使用Promise::new()函数创建Promise对象,通过Promise::then()方法处理异步操作结果。示例代码如下:

rust 复制代码
use js_sys::{ Promise, JsValue };
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::console;

// 这是一个辅助函数,用于实现延时
async fn sleep(ms: i32) {
    let promise = Promise::new(&mut |resolve, _| {
        let window = web_sys::window().unwrap();
        window
            .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, ms)
            .unwrap();
    });
    JsFuture::from(promise).await.unwrap();
}

#[wasm_bindgen]
pub async fn get_data_after_delay() -> Result<JsValue, JsValue> {
    console::log_1(&"Rust: Waiting for 2 seconds...".into());
    
    // 模拟一个异步操作
    sleep(2000).await;
    
    let message = "Rust: Here is your data!";
    console::log_1(&message.into());

    // 返回一个成功的结果
    Ok(JsValue::from_str(message))
}

在JavaScript 中调用:

javascript 复制代码
import init, { get_data_after_delay } from './pkg/your_pkg_name.js';

const run = async () => {
    await init();
    get_data_after_delay().then((data) => {
        console.log('Received data:', data); // Received data: Rust: Here is your data!
    });
    // Rust: Waiting for 2 seconds...
    // Received data: Rust: Here is your data!
};

run();

三 js-sys 与 wasm-bindgen 的关系

js-sys 通常作为 wasm-bindgen 的底层支持库。

wasm-bindgen 是一个用于在 Rust 和 JavaScript 之间进行高效交互的工具,它提供了更高级的接口和类型安全的绑定。

js-sys 提供的裸绑定能力使得 wasm-bindgen 能够在底层直接操作 JavaScript 对象,从而实现更复杂的交互逻辑。

例如,在将 Rust 的函数暴露为 JavaScript 可调用的函数时,wasm-bindgen会利用js-sys来处理参数和返回值的类型转换,使得 Rust 函数能够与 JavaScript 环境无缝对接。

两者协同工作,使得开发者可以在 Rust 中更方便、更灵活地操作 JavaScript 对象,实现强大的 WebAssembly 应用 。

四 何时选择使用 js-sys

在开发中,当我们需要直接调用 JavaScript 内置对象或全局方法时, 或者性能敏感的场景下,js-sys就派上用场了 。

在以下场景中可以优先考虑使用 js-sys

  • 需要极致的灵活性: 当处理非常动态或者结构不定的 JavaScript 对象时,js-sysReflect 是你的最佳拍档。

  • web-sys 中没有绑定: web-sys 虽然庞大,但并非涵盖了所有 Web API 或所有 JavaScript 特性。当你需要使用的某个 API 在 web-sys 中找不到时,可以尝试用 js-sys 来"手动"构建调用。

  • 性能敏感的底层库开发: 如果在编写一个供其他 Wasm 模块使用的底层库,直接使用 js-sys 可以减少不必要的抽象层,可能会带来微小的性能优势。

  • 与现有的 JavaScript 库深度集成: 当需要与一个期望接收原生 JS 对象(如 Date, Error)的 JavaScript 库交互时,js-sys 可以让你轻松创建出符合要求的对象。

五 总结

js-syswasm-bindgen 生态中的底层库,提供对 JavaScript 原生对象的裸绑定。

可以用它在 Rust 中直接创建和操作 Date, Array, Object, Promise 等 JavaScript 对象。Reflect API 是动态操作 Object 属性的利器。

js-sys 赋予了我们极大的灵活性,是处理动态数据和底层交互的强大工具。

在实际开发中,js-sys 适用于需要更底层、更灵活地与 JavaScript 运行时交互的场景,例如直接操作 JavaScript 原生对象、处理复杂的异步逻辑或实现高性能的交互。

wasm-bindgen 则更适合在需要类型安全和高级抽象的场景中使用。两者结合使用,可以充分发挥各自的优势,实现更高效、更安全的 Rust 与 JavaScript 交互。

相关推荐
Yueeyuee_2 分钟前
【C#学习Day15笔记】拆箱装箱、 Equals与== 、文件读取IO
笔记·学习·c#
এ旧栎19 分钟前
Gitee
笔记·gitee·学习方法
kfepiza25 分钟前
vim的`:q!` 与 `ZQ` 笔记250729
linux·笔记·编辑器·vim
Emotion亦楠1 小时前
Java 学习笔记:常用类、String 与日期时间处理
java·笔记·学习
养海绵宝宝的小蜗2 小时前
OSPF笔记整理
网络·笔记·智能路由器
没见过西瓜嘛3 小时前
数据仓库、数据湖与湖仓一体技术笔记
数据仓库·笔记
勇敢牛牛_4 小时前
【OneAPI】网页搜索API和网页正文提取API
rust·oneapi
Mr Sorry4 小时前
TIME WEAVER: A Conditional Time Series Generation Model论文阅读笔记
论文阅读·笔记
寄思~4 小时前
学习笔记:封装和单继承
开发语言·笔记·python·学习
I'm a winner4 小时前
LaTeX 复杂图形绘制教程:从基础到进阶
经验分享·笔记·科技