在使用 C 語言嵌入 SpiderMonkey JavaScript 引擎時,有時需要建立一個「可被呼叫」的物件,但它並非標準的 Function 物件。例如實作一個 Hash 物件,希望能像 h(key) 這樣直接以函數呼叫語法取得鍵值,而 h 本身是普通物件而非函數。
關鍵在於 JSClass 結構中的 call 欄位。只要將其指向一個符合 JSBool (*)(JSContext*, JSObject*, uintN, jsval*, jsval*) 簽名的 C 函數,該物件在 JS 中即可被呼叫。
static JSBool
call_hash(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
/* 注意:此處的 obj 指向全域物件,而非被呼叫物件本身 */
*rval = JSVAL_NULL;
return JS_TRUE;
}
static JSClass hash_class = {
"Hash", JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
finalize_hash, /* finalize */
0, /* reserved0 */
0, /* reserved1 */
call_hash, /* call — 關鍵在這裡 */
0, 0, 0, 0, 0 /* 其餘保留 */
};
官方文件未說明:當引擎呼叫 call_hash 時,參數 obj 是全域物件,並非被呼叫的 Hash 實例。經詢問郵件列表,獲得一個「黑科技」:argv[-2] 即為被呼叫物件本身。
這意味著可在回調中透過 JSObject *callee = JSVAL_TO_OBJECT(argv[-2]); 取得真正的 Hash 物件,進而存取其私有資料。
雖然 SpiderMonkey 功能強大、歷史悠久,但其 C API 設計上存在不少反直覺之處(如上述 argv[-2] 技巧),導致開發體驗不佳。若專案允許,改用 V8、QuickJS 或 Duktape 等現代引擎或許能減少此類坑洞。