前言
- 之前老是聽別人提到
WebAssembly這個詞,一直對其比較模糊,不能理解是個啥東西,后來自己實踐了一下,發現其實就是一種提高代碼性能的手段,
簡介
- WebAssembly 是一種運行在現代網路瀏覽器中的新型代碼,并且提供新的性能特性和效果,它設計的目的不是為了手寫代碼而是為諸如 C、C++和 Rust 等低級源語言提供一個高效的編譯目標,(解釋來自MDN)
- 通俗一點來講,就是利用一些C、C++、Rust等偏底層的一些語言去實作部分功能并編譯成二進制檔案然后暴露給第三方平臺(可能是瀏覽器端,也可能是服務端,還有可能是客戶端),可以豐富和優化相關的應用,
- 當然了,其實也有類似asm.js這種非底層語言去實作,但是它編譯后的二進制檔案減少了編譯的程序,其實也是可以提高代碼的性能,
開始嘗試
準備作業
- 我準備使用rust來演示一下,所以需要用到的第三方環境如下
- 安裝
Rust,curl https://sh.rustup.rs -sSf | sh -s -- --help,windows上可以下載https://static.rust-lang.org/rustup/dist/i686-pc-windows-gnu/rustup-init.exe,安裝成功后可以在命令列輸入rustc -V以及cargo -V來看看是否安裝成功,cargo是Rust一個包管理器,類似npm之于nodejs, - 安裝
wasm-pack,參照官網https://rustwasm.github.io/wasm-pack/installer/, Rust基礎語法,可以參考https://course.rs/about-book.html這位大佬撰寫的書,不枯燥又有趣,比官方的中文檔案可讀性更強,
- 安裝
開始
- 使用上面安裝好的
wasm-pack新建一個空專案,wasm-pack new test-webassembly, - 查看目錄結構
test-webassembly
src
lib.rc // 主入口
utils.rs // 依賴的工具方法tests
web.rs // 測驗檔案
.appveyor.yml // 專案的前置依賴配置
.gitignore // git忽略的檔案配置
.travis.yml // 該專案的CI環境組態檔
cargo.toml // 該專案的組態檔,類似package.json
README.md // 解釋檔案
重點檔案分析
- lib.rs,默認自帶的這個檔案只實作了一個
greet方法,并暴露給了瀏覽器端,里面呼叫了alert方法,use wasm_bindgen::prelude::*; // 引入wasm_bindgen::prelude下所有的類和方法 #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; // 一般不需要用,可以選擇注釋 #[wasm_bindgen] extern { fn alert(s: &str); // 表示第三方環境可以直接用的方法定義,編譯的時候會直接去呼叫第三方環境中的同名方法 } #[wasm_bindgen] // 類似ts中的裝飾器,打上這個標簽,表示是需要與第三方環境去互動的,通俗一點,就是這個地方會被打包到第三方環境中, pub fn greet() { alert("Hello, test-webassembly!"); } - Cargo.toml,這個檔案主要就是一些專案配置資訊了,
[package] name = "test-webassembly" // 包名 version = "0.1.0" // 包版本 authors = ["xxx"] // 作者名 edition = "2018" // rust版本 [lib] // target設定,類似webpack種的target,將其打包成什么樣的包 crate-type = ["cdylib", "rlib"] // 分別代表動態系統庫和rust靜態庫 [features] // 用來條件編譯 default = ["console_error_panic_hook"] // 這個可以刪掉不需要 [dependencies] wasm-bindgen = "0.2.63" // 這個就是主要的處理rust代碼轉化成webassembly的包 # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. # console_error_panic_hook = { version = "0.1.6", optional = true } 這個其實如果不是特別需要也可以不用 # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size # compared to the default allocator's ~10K. It is slower than the default # allocator, however. # wee_alloc = { version = "0.4.5", optional = true } 這個一般用不上,注釋掉就可以 [dev-dependencies] wasm-bindgen-test = "0.3.13" // 用來測驗的包,自己寫一般可以不用 [profile.release] # Tell `rustc` to optimize for small code size. opt-level = "s"
改造
接下來實作一個簡單的圖片處理工具,幫助web端更快的處理圖片內容,
- 會用到
image這個rust包,所以先改造它的Cargo.toml,
[dependencies]
wasm-bindgen = "0.2.63"
// 在之前的dependencies下面添加image包
image = "0.24.6"
- 就以處理圖片的其中內置的一個灰度方法來看一下,接下來改造
lib.rs檔案,
// 新增一個灰度方法,接受一個位元組型別的集合,回傳一個新的位元組型別的集合
#[wasm_bindgen]
pub fn gray(_array: &mut [u8]) -> Vec<u8> {
let mut img = image::load_from_memory(_array).unwrap(); // 將傳入的u8集合轉化成一個image物件
img = img.grayscale(); // 呼叫第三方包的灰度方法得到新的圖片
let mut bytes: Vec<u8> = Vec::new(); // 定義一個新的u8集合
img.write_to(&mut Cursor::new(&mut bytes), image::ImageOutputFormat::Png).unwrap(); // 將變換后的image轉化成新的u8集合
bytes // 將u8集合作為回傳值拋出
}
- 運行命令
wasm-pack build --release --target web,將rust檔案打包成目標為web專案的可用二進制檔案以及加載的js檔案,可以看到專案多了一個pkg檔案夾pkg
.gitignore
package.json
README.md
test_webassembly_bg.wasm // 二進制檔案
test_webassembly_bg.wasm.d.ts
test_webassembly.d.ts
test_webassembly.js // 這個就是加載這個二進制的檔案
// test_webassembly.js這個檔案主要看幾個重點地方,其實不看也行,這個地方wasm-pack已經幫你處理好了,基本只需要學會用就行
// 加載二進制后的處理,回傳暴露出的模塊的實體
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
// 在支持instantiateStreaming的情況下,優先使用instantiateStreaming加載對應的二進制檔案
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
// 不支持的時候,就使用instantiate檔案來加載
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
// 針對已經決議完的實體,做一些初始化操作,拋出模塊實體的exports
function finalizeInit(instance, module) {
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
cachedInt32Memory0 = null;
cachedUint8Memory0 = null;
return wasm;
}
// 檔案暴露的入口
async function init(input) {
if (typeof input === 'undefined') {
input = new URL('test_webassembly_bg.wasm', import.meta.url); // 默認的二進制檔案地址
}
const imports = getImports();
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input); // 加載對應的二進制檔案
}
initMemory(imports); // 這個就是之前上面申請空間的,可以注釋掉的,不用看
const { instance, module } = await load(await input, imports); // 呼叫webassembly的api去加載二進制檔案
return finalizeInit(instance, module); // 得到模塊中拋出的exports
}
在html中使用,新建一個index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="file" multiple="false" id="file">
<div id="box"></div>
<div>
<button id="gray-btn">灰度</button>
</div>
<div id="target"></div>
<script type="module">
import init, { gray } from './pkg/test_webassembly.js';
await init();
const file = document.getElementById('file');
const grayBtn = document.getElementById('gray-btn');
let current = null;
let uint8Array = [];
file.addEventListener('change', (e) => {
current = e.target.files[0];
var reader = new FileReader();
reader.readAsDataURL(current);
reader.onload = function() {
const img = new Image();
img.src = https://www.cnblogs.com/aloneMing/archive/2023/04/14/this.result;
document.querySelector('#box').appendChild(img);
}
});
grayBtn.addEventListener('click', () => {
var reader = new FileReader();
reader.readAsArrayBuffer(current);
reader.onload = function () {
uint8Array = new Uint8Array(this.result);
let newUnit8Array = gray(uint8Array); // 呼叫webassembly提供的灰度方法獲取到灰度之后的結果
const img = new Image();
let blob = new Blob([newUnit8Array], { type: 'image/png' });
img.src = https://www.cnblogs.com/aloneMing/archive/2023/04/14/window.URL.createObjectURL(blob);
document.querySelector('#target').appendChild(img);
}
})
</script>
</body>
</html>
使用npx http-server --port 3000啟動靜態服務器,因為webassembly是不支持本地的檔案的,下面就是最終效果,先選擇一張圖片,之后點擊灰度就能快速得到一張灰度的圖片

小結
WebAssembly是一個很有意思的技術方向,它為web應用提供了很多可能,圖片處理,視頻決議、加密等等一些復雜的場景都可以變得更輕便化,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/550161.html
標籤:其他
