การอ่าน Exports
หลังจาก instantiation แล้ว instance.exports คือ JavaScript object ธรรมดา ทุก symbol ที่ WAT module ประกาศ export ไว้จะปรากฏเป็น property บน object นั้น — exported functions สามารถเรียกใช้ได้เหมือนฟังก์ชันปกติ, exported memory จะเป็น WebAssembly.Memory, และ exported globals จะเป็น WebAssembly.Global objects
Exported Functions
หัวข้อที่มีชื่อว่า “Exported Functions”Exported Wasm functions ทำงานเหมือน JavaScript functions ทั่วไป คุณเรียกใช้ได้โดยตรง ส่ง JavaScript numbers เป็น arguments และรับค่า JavaScript กลับมา การแมปสำหรับ numeric types ส่วนใหญ่นั้นตรงไปตรงมา: i32, f32 และ f64 ทั้งหมดจะกลายเป็น number ใน JavaScript
(module (func (export "add") (param $a i32) (param $b i32) (result i32) local.get $a local.get $b i32.add))const { instance } = await WebAssembly.instantiate(bytes, {});console.log(instance.exports.add(10, 32)); // 42การแปลงค่า Number — i64 และ BigInt
หัวข้อที่มีชื่อว่า “การแปลงค่า Number — i64 และ BigInt”ประเภท number ของ JavaScript ใช้มาตรฐาน IEEE-754 double precision ซึ่งสามารถแทนค่าจำนวนเต็มได้แม่นยำเพียงถึง 2^53 − 1 เท่านั้น (Number.MAX_SAFE_INTEGER) ส่วน Wasm i64 คือจำนวนเต็มขนาด 64 บิตเต็ม ซึ่งรองรับค่าที่เกินช่วงนั้นมาก
เพื่อป้องกันการสูญเสียความแม่นยำโดยไม่ตั้งใจ WebAssembly JS API จะแปลงค่า return ประเภท i64 ให้เป็น JavaScript BigInt แทนที่จะเป็น number ผลลัพธ์จากการเรียกฟังก์ชันที่คืนค่า i64 จะมี typeof result === 'bigint' ไม่ใช่ 'number'
(module (func (export "bignum") (result i64) i64.const 9007199254740993))const result = instance.exports.bignum();console.log(result); // 9007199254740993nconsole.log(typeof result); // "bigint"สังเกตว่าการคำนวณระหว่าง BigInt กับ number ต้องแปลงชนิดข้อมูลก่อนอย่างชัดเจน — ไม่สามารถใช้ตัวดำเนินการอย่าง + หรือ * ร่วมกันได้โดยตรงหากไม่แปลงชนิดก่อน
Exported Globals
หัวข้อที่มีชื่อว่า “Exported Globals”Wasm module สามารถ export globals ร่วมกับ functions ได้ ค่า global ที่ถูก export จะถูกเข้าถึงใน JavaScript ในรูปแบบ WebAssembly.Global object โดยค่าตัวเลขปัจจุบันจะอยู่ที่ property .value
(module (global (export "PI") f64 (f64.const 3.14159)))console.log(instance.exports.PI.value); // 3.14159Exported Memory
หัวข้อที่มีชื่อว่า “Exported Memory”เมื่อ module export linear memory ออกมา JavaScript จะได้รับ WebAssembly.Memory object bytes ดิบสามารถเข้าถึงได้ผ่าน property .buffer ในรูปแบบ ArrayBuffer ซึ่งคุณสามารถห่อด้วย typed array view เพื่ออ่านหรือเขียนข้อมูลแต่ละ byte ได้
(module (memory (export "mem") 1))const mem = instance.exports.mem; // WebAssembly.Memoryconst view = new Uint8Array(mem.buffer);console.log(view.length); // 65536 (1 page = 64 KiB)ลองรันดู
หัวข้อที่มีชื่อว่า “ลองรันดู”Module ด้านล่างนี้ export ฟังก์ชัน add, ฟังก์ชัน bignum ที่คืนค่า i64 ซึ่งเกิน Number.MAX_SAFE_INTEGER, และ global VERSION ตัวรันจะอ่านค่าทั้งสามและแสดงค่าพร้อมชนิดข้อมูล
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
instantiateStreaming | เริ่มคอมไพล์ทันทีที่ bytes เริ่มไหลมาจาก network ไม่ต้องรอดาวน์โหลดครบ ทำให้ exports พร้อมใช้เร็วขึ้น | ต้องพึ่ง server ที่ตั้งค่า Content-Type: application/wasm ให้ถูกต้อง ไม่งั้น reject ทันที |
instantiate แบบ buffer เต็มไฟล์ | เขียนง่าย ควบคุม flow ได้ตรงไปตรงมา ใช้ได้แม้ไม่ควบคุม MIME type ของ server | ต้องรอดาวน์โหลดครบก่อนเริ่มคอมไพล์ exports จึงพร้อมใช้ช้ากว่า |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ลืมแปลงผลลัพธ์ที่เป็น
i64ให้เป็นBigIntก่อนนำไปคำนวณกับnumberธรรมดา ทำให้โค้ดโยนTypeErrorหรือปัดค่าคลาดเคลื่อนแบบเงียบๆ - อ่าน/เขียนข้อมูลก้อนใหญ่ผ่าน exported memory ทีละไบต์หรือทีละ call แทนที่จะสร้าง typed array view ครอบ
.bufferแล้วอ่าน-เขียนรวดเดียว ทำให้เสีย performance โดยไม่จำเป็น - โหลดทั้งไฟล์ด้วย
arrayBuffer()ก่อนแล้วค่อย instantiate เพื่อไปอ่าน exports แทนที่จะใช้instantiateStreamingซึ่งคอมไพล์ไปพร้อมกับการดาวน์โหลด
💡 ตัวอย่างจากของจริง
Squoosh (เครื่องมือบีบอัดภาพของ Google) export ฟังก์ชัน codec หลายตัว (MozJPEG, WebP, AVIF ฯลฯ) จาก Wasm module แล้วเรียกใช้ exports เหล่านั้นตรงๆ จาก JS พร้อมอ่านผลลัพธ์ภาพผ่าน exported memory เพื่อให้ได้ throughput สูงสุด