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 交互。

相关推荐
ssshooter几秒前
Tauri 踩坑 appLink 修改后闪退
前端·ios·rust
布列瑟农的星空1 小时前
前端都能看懂的rust入门教程(二)——函数和闭包
前端·后端·rust
tingshuo291714 小时前
S001 【模板】从前缀函数到KMP应用 字符串匹配 字符串周期
笔记
蚂蚁背大象1 天前
Rust 所有权系统是为了解决什么问题
后端·rust
布列瑟农的星空1 天前
前端都能看懂的rust入门教程(五)—— 所有权
rust
Java水解2 天前
Rust嵌入式开发实战——从ARM裸机编程到RTOS应用
后端·rust
Pomelo_刘金2 天前
Rust:所有权系统
rust
Ranger09292 天前
鸿蒙开发新范式:Gpui
rust·harmonyos
BigByte4 天前
我用 6 个 WASM 编码器干掉了 Canvas.toBlob(),图片压缩率直接提升 15%
性能优化·webassembly·图片资源
Tlink5 天前
WebAssembly:十年磨一剑,这些实践案例让我看到了它的真面目
webassembly·webassembly实践