什么是 webassembly
在 2019 年 12 月之前,如果你要編寫一個web頁面,那一定離不開 html、css、js 這三個好兄弟。在 2019 年 12 月之后 W3C 宣布 webassembly 加入了他們。為什么要在三兄弟后加入 webassembly ?它和之前的有什么區別么?以 js 為對比,我們具體看一下它們的區別。
wasm 與 js 的區別
js 是一種解釋型語言,它代碼運行之前不會進行編譯工作,而是在執行的過程中實時編譯。為了讓邊編譯邊執行能夠順利進行,我們擁有了 js 引擎。
wasm 則與之不同,它本身不是一種編程語言,而是一種字節碼的標准,可以通過不同種類的高級編程語言,比如 Rust、Go、Python 等等,通過各自編譯器將代碼轉換成 .wasm 文件,放入到瀏覽器預先做好的 wasm 虛擬機當中運行。
同時這種與 js 不同,可以預先運行的特色,也給 wasm 帶來了一些優勢:
-
擁有一套完成的語義: wasm 這套體積小且加載快的二進制格式的完整語義,可以讓硬件充分發揮能力以達到原生的執行效率。
-
編譯和優化時間所需時間較少: 相比 js 作為一個動態類型需要多次編譯代碼,wasm 讓文件在推送到服務器前就進行了眾多優化,能夠有效減少編譯和優化時間。
-
執行速度更快: wasm 是二進制文件,它的指令更接近機器碼,執行起來的速度相比 js 更快。
-
垃圾回收效率更高: wasm 目前並不支持自動垃圾回收,垃圾回收都是手動控制的,比自動垃圾回收效率更高。
當然如果只是這么說,我們並不能很直觀的感受出這些優勢究竟有多大,剛好目前瀏覽器已經支持了以 wasm 規范的虛擬機。
接下來,我們通過一個 Chrome 與 Safari 的實例測試,來感受一下。
wasm與js執行速度比較
首先我們用原生的 js 寫一段 fib 代碼測試時間,代碼內容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function fibonacci(n) {
if (n == 0 || n == 1)
return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
function fibonacciTime() {
console.time("js")
fibonacci(40)
console.timeEnd("js")
}
fibonacciTime()
</script>
</body>
</html>
用 live-server 測試時間,用時 1275 毫秒到 1329 毫秒。
然后我們用 Rust 轉義成 wasm 再次測試。特別需要注意的是,測試用到的算法都是最普通的遞歸迭代,在實際使用中我們還可以使用動態規划來再次優化。
言歸正傳,然后我們用 rust-wasm 編譯器將用 rust 寫好的 fib 代碼轉換成 wasm 文件。
下載 wasm 編譯器:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
配置 Cargo.toml:
[package]
name = "wasm"
version = "0.2.0"
authors = ["hzjsea"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
path = "src/main.rs"
[dependencies]
wasm-bindgen = "0.2.48"
chrono = "0.4.19"
main.rs:
use chrono::Utc;
use wasm_bindgen::prelude::*;
use std::time::Instant;
#[wasm_bindgen]
pub fn fib(n: i32) -> i32 {
match n {
0 => 1,
1 => 1,
_ => fib(n - 1) + fib(n - 2),
}
}
pub fn main(){
let result = fib(40);
println!("{:?}", result);
}
掛載wasm文件,即類似 vue,使用 index.html 把 wasm.js 文件掛載起來:
<script type="module">
main()
async function main() {
const wasm = await import('/pkg/wasm.js')
await wasm.default('/pkg/wasm_bg.wasm')
console.time("rust")
console.log(wasm.fib(40))
console.timeEnd("rust")
}
function fibonacci(n) {
if(n==0 || n == 1)
return n;
return fibonacci(n-1) + fibonacci(n-2);
}
function fibonacciTime(){
console.time("js")
fibonacci(40)
console.timeEnd("js")
}
fibonacciTime()
</script>
之后用編譯器編譯 Rust 代碼生成 .wasm 文件:
wasm-pack build --target web --no-typescript --mode normal
然后我們可以明顯看到,在相同的 live-server 測試下時間相差一倍左右。
因為此次編譯指定不生成 wasm 的 ts 版本。所有只有上面的幾個文件,其中:
- package.json
{
"name": "wasm",
"collaborators": [
"hzjsea"
],
"version": "0.2.0",
"files": [
"wasm_bg.wasm",
"wasm.js"
],
"module": "wasm.js",
"sideEffects": false
}
指定打包的各類屬性
- wasm_bg.wasm
wasm.js轉義后的二進制文件
- wasm.js
由rust代碼轉移過來的wasm.js文件。
wasm 的組件庫
看完了實例測試,我們不能略過測試中提到的 Rust。Rust 中有個類似於 react 的框架,叫做Yew。關於 Yew 官方[https://github.com/yewstack/yew]描述如下:
Yew is a modern Rust framework for creating multi-threaded front-end web apps with WebAssembly.
-
Features a macro for declaring interactive HTML with Rust expressions. Developers who have experience using JSX in React should feel quite at home when using Yew.
-
Achieves high performance by minimizing DOM API calls for each page render and by making it easy to offload processing to background web workers.
-
Supports JavaScript interoperability, allowing developers to leverage NPM packages and integrate with existing JavaScript applications.
計時器
大致看完了 Rust,如果你還想看更多的 WebAssembly 流程,官方提供的一個計時器的練手項目可以滿足你的需求
項目地址 https://yew.rs/docs/zh-CN/getting-started/build-a-sample-app
主要可以看下 lib.rs 這個文件
總結與思考
雖然 webassembly 作為一種新的 web 技術常常被提起,但是因為其工具鏈的調試困難,包體積過大等等問題還在解決的過程中,同時也表明了 wasm 並不可能在短時間內直接代替 js,他們之間更多的是一種互補合作的關系。但不可否認的是,適合 webassembly 場景的項目會在未來的一段時間內不斷的出現,大家可以多多了解一下。