Skip to content

オブジェクトと配列

このページでは、ホスト(Zig)側から JavaScript のオブジェクト・配列を作成し、プロパティや要素を読み書きする方法を説明します。Value 自体の基本(作成・変換・型判定)については Value の扱い方 を、関数の登録については ホスト関数とコールバック を参照してください。

オブジェクトを作る

ctx.makeObject() は空の JavaScript オブジェクト({} 相当)を作成します。

const obj = try ctx.makeObject();

他の make* 系関数と同様に、返された Value は GC のルートに繋がっていません。すぐに setGlobal/setProperty/setIndex などでオブジェクトツリーにアタッチするか、protect/HandleScope.pin で保護してください(詳しくは メモリ管理 を参照)。

プロパティを設定する

try ctx.setProperty(obj, "name", try yanmajs.makeString(ctx, "yanmajs"));
try ctx.setProperty(obj, "version", yanmajs.makeInt(ctx, 1));
  • ctx.setProperty(obj_val: Value, name: []const u8, val: Value) !void

obj_val がプレーンオブジェクトでない場合(数値や配列以外の非オブジェクト値など)は何もしません。既存のプロパティがあれば単純に上書きします。

プロパティディスクリプタで定義する

より細かい制御(読み取り専用にする、非列挙にする、アクセサにするなど)が必要な場合は defineProperty を使います。

pub const PropertyDescriptor = struct {
    value: ?Value = null,
    getter: ?Value = null,
    setter: ?Value = null,
    writable: ?bool = null,
    enumerable: ?bool = null,
    configurable: ?bool = null,
};
try ctx.defineProperty(obj, "id", .{
    .value = yanmajs.makeInt(ctx, 42),
    .writable = false,
    .enumerable = true,
    .configurable = false,
});
  • getter/setter のどちらかが指定されている場合はアクセサプロパティとして定義されます。getter/setter には makeFunction で作った Value(HostMethod)を渡すことで、native なアクセサとして機能させられます(ホスト関数とコールバック を参照)。
  • データプロパティ(value を指定する場合)の writable/enumerable/configurable省略時は true です。
  • アクセサプロパティ(getter/setter を指定する場合)の enumerable/configurable省略時は false です(Object.defineProperty のデフォルトと同じ挙動)。
const getX: yanmajs.HostMethod = struct {
    fn call(c: *Context, this: Value, args: []const Value) !Value {
        _ = c;
        _ = args;
        return yanmajs.getProperty(this, "_x");
    }
}.call;

try ctx.defineProperty(obj, "x", .{
    .getter = try ctx.makeFunction("get x", getX),
    .enumerable = true,
});

プロパティを読み取る

プロパティの読み取りは Context のメソッドではなく、スタンドアロン関数の getProperty で行います。

const name = yanmajs.getProperty(obj, "name");
  • yanmajs.getProperty(val: Value, name: []const u8) Value — 対象がプレーンオブジェクトでない場合(Proxy はターゲットを辿ります)は undefined を返す安全な実装です。存在しないプロパティも undefined になります。

配列を作る

const arr = try ctx.makeArray();
  • ctx.makeArray() !Value — 空の配列([] 相当)を作成します。

配列要素を設定する

try ctx.setIndex(arr, 0, yanmajs.makeInt(ctx, 1));
try ctx.setIndex(arr, 1, yanmajs.makeInt(ctx, 2));
  • ctx.setIndex(arr_val: Value, index: usize, val: Value) !void

途中のインデックスを飛ばして設定した場合、間の要素は JavaScript の「空要素(hole)」として扱われます(内部的に埋められ、読み取ると undefined になります)。arr_val が配列でない場合は何もしません。

配列要素を読み取る・長さを取得する

こちらもスタンドアロン関数です。

const len = yanmajs.getLength(arr);
var i: usize = 0;
while (i < len) : (i += 1) {
    const item = yanmajs.getIndex(arr, i);
    // ...
}
  • yanmajs.getIndex(val: Value, index: usize) Value — 範囲外や非配列の場合は undefined
  • yanmajs.getLength(val: Value) usize — 非配列の場合は 0

ホストオブジェクト(host object)

ネイティブリソース(ファイルハンドル、コネクション、内部状態など)を JavaScript オブジェクトに紐付けたい場合は makeHostObject を使います。

pub fn makeHostObject(
    ctx: *Context,
    data: ?*anyopaque,
    finalizer: ?*const fn (?*anyopaque) void,
) !Value
const Resource = struct { handle: i32 };

const FinalizerImpl = struct {
    fn cleanup(data: ?*anyopaque) void {
        const res: *Resource = @ptrCast(@alignCast(data.?));
        // ここでネイティブリソースを解放する
        std.heap.page_allocator.destroy(res);
    }
};

const resource = try std.heap.page_allocator.create(Resource);
resource.* = .{ .handle = 42 };

const obj = try ctx.makeHostObject(@ptrCast(resource), FinalizerImpl.cleanup);

makeHostObject は「プレーンオブジェクトを作成し、host_data/host_finalizer を同時に設定する」ショートカットです。finalizer はこのオブジェクトが GC によって回収されたタイミングでエンジンから呼び出され、data を引数として渡されます。プロトタイプやメソッドは含まれないため、必要に応じて後述の setPrototypesetProperty(+ makeFunction)で個別にアタッチしてください。

  • yanmajs.getHostData(val: Value) ?*anyopaque — 紐付いた host data を取得(非対応の値やホストデータ未設定の場合は null)。
  • yanmajs.setHostData(val: Value, data: ?*anyopaque, finalizer: ?*const fn (?*anyopaque) void) void — 既存のオブジェクトに後から host data/finalizer を設定する。

注意: finalizer は GC のマーク&スイープ実行時にホスト側で呼ばれます。呼び出しタイミングはホストの制御外(自動 GC か collectGarbage() 呼び出し時)なので、finalizer の中で JavaScript の実行(callFunction など)を行うのは避けてください。

プロトタイプチェーン

setPrototype を使うと、オブジェクトの [[Prototype]] を設定できます。複数のホストオブジェクトをこれで連鎖させれば、DOM のような多段プロトタイプチェーンを構築できます。

ctx.setPrototype(obj_val: Value, proto_val: Value) void

proto_val に JavaScript の null を渡すとプロトタイプチェーンを切り離せます。obj_val/proto_val がプレーンオブジェクトでない場合(null を除く)は何もしません。

例: DOM ライクなオブジェクトツリーを作る

Node ← Element ← HTMLElement のようなプロトタイプチェーンを、makeHostObject + setPrototype + makeFunction を組み合わせて構築する例です。

const nodeType: yanmajs.HostMethod = struct {
    fn call(c: *Context, this: Value, args: []const Value) !Value {
        _ = args;
        return yanmajs.getProperty(this, "_nodeType");
    }
}.call;

// 1. 各レベルのプロトタイプオブジェクトを用意する
const node_proto = try ctx.makeObject();
try ctx.defineProperty(node_proto, "nodeType", .{
    .getter = try ctx.makeFunction("get nodeType", nodeType),
    .enumerable = true,
});

const element_proto = try ctx.makeObject();
ctx.setPrototype(element_proto, node_proto);

const html_element_proto = try ctx.makeObject();
ctx.setPrototype(html_element_proto, element_proto);

// 2. 実インスタンスを host object として作る
var elem_state = ElementState{ .tag = "div" };
const div = try ctx.makeHostObject(@ptrCast(&elem_state), null);
try ctx.setProperty(div, "_nodeType", yanmajs.makeInt(ctx, 1));
ctx.setPrototype(div, html_element_proto);

try ctx.setGlobal("div", div);
const result = try ctx.eval("div.nodeType"); // 1(プロトタイプチェーンを辿って解決される)

次のステップ