Rust & WebAssembly:探索js-sys的奇妙世界
一 js-sys 库简介
js-sys
是 wasm-bindgen
工具链中的一个底层核心库。它提供了对 JavaScript 全局对象和内置对象的裸绑定(raw bindings),即对 JavaScript 全局对象和内置类型的原生绑定。它不添加抽象层,让你在 Rust 中直接操作 JS 原生对象,就像在 JavaScript 中一样自然。
通过js-sys
在 Rust 中创建和操作 JavaScript 的Object
、Array
、Function
等对象,就像在 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-sys
和Reflect
是你的最佳拍档。 -
web-sys
中没有绑定:web-sys
虽然庞大,但并非涵盖了所有 Web API 或所有 JavaScript 特性。当你需要使用的某个 API 在web-sys
中找不到时,可以尝试用js-sys
来"手动"构建调用。 -
性能敏感的底层库开发: 如果在编写一个供其他 Wasm 模块使用的底层库,直接使用
js-sys
可以减少不必要的抽象层,可能会带来微小的性能优势。 -
与现有的 JavaScript 库深度集成: 当需要与一个期望接收原生 JS 对象(如
Date
,Error
)的 JavaScript 库交互时,js-sys
可以让你轻松创建出符合要求的对象。
五 总结
js-sys
是 wasm-bindgen
生态中的底层库,提供对 JavaScript 原生对象的裸绑定。
可以用它在 Rust 中直接创建和操作 Date
, Array
, Object
, Promise
等 JavaScript 对象。Reflect
API 是动态操作 Object
属性的利器。
js-sys 赋予了我们极大的灵活性,是处理动态数据和底层交互的强大工具。
在实际开发中,js-sys
适用于需要更底层、更灵活地与 JavaScript 运行时交互的场景,例如直接操作 JavaScript 原生对象、处理复杂的异步逻辑或实现高性能的交互。
而 wasm-bindgen
则更适合在需要类型安全和高级抽象的场景中使用。两者结合使用,可以充分发挥各自的优势,实现更高效、更安全的 Rust 与 JavaScript 交互。