JavaScriptのProxyがよくわからないという人向けにProxyはどのように使用することができるのかを簡単な例を通して説明を行っています。

ProxyとReflectはVue3のReactivityなどに利用されている機能でさまざまなライブラリやフレームワークでも使われています。

JavaScriptのProxyとは

Proxyには”代理”という意味があり、オブジェクトに対して直接ではなくProxyを経由してアクセスを行うことで本来の処理を変更したり、追加の処理を加えること(通常のオブジェクトの処理に対して割り込みを行い本来の振る舞いを変える)ができます。

あるサイトのProxyの例をこんな身近な例を利用して記述していました。近所のカレー屋は直接出前を注文することができますがUberを経由しても行うことができます。どちらも”最終的にはカレーが宅配されます”がUberを経由させることで到着までのステータスをリアルタイムに確認したり店の評価をつけたりと追加な機能を利用することができます。本来の出前に対して、UberをProxyを利用した処理と考えることできます。なんとなくわかりましたかね。
fukidashi

Proxyの説明についてはネット上にあふれる文書を読んでも??の人も多いかと思います。ネット上の文書を読んで理解できなかった人は実際にコードで確認しましょう。

Proxyを使った簡単な例

userオブジェクトを作成し、nameプロパティの値を取得したい場合通常は下記の方法で取得することができます。


 let user = {
  firstName: "John"
}

console.log(user.firstName)

Proxyを利用する場合はnew Proxyを使って新たにProxyオブジェクトproxy_userを作成し、作成したproxy_userを経由してアクセスを行います。


let user = {
  firstName: "John"
}

let proxy_user = new Proxy(user,{})

console.log(proxy_user.firstName)

実行すると結果はどちらもJohnが表示されます。

Proxyの構文は2つの引数をとることができま、以下のようになります。上記の例では第二引数が空のオブジェクトでしたが空のオブジェクトの部分をhandlerと呼びここに処理を加えていきます。第一引数のtargetにはオブジェクトを設定します。

let proxy = new Proxy(target, handler)

handlerの中には traps(トラップ)と呼ばれる予め決められたメソッドを記述することで処理を追加することができます。trapsには値を取得する場合に利用できるgetメソッド、値を設定したい場合に利用できるsetメソッドが存在します。handlerを通してオブジェクトの操作に別の処理を追加します。handlerが空の場合は元々のオブジェクトと同じ操作を行うことができます。

その他のメソッドについてはMDN web docsに記述されています。

getとsetメソッド を説明し、その後deletePropertyメソッドの説明を行っていきます。

getメソッドを使って処理を追加

handlerの中にオブジェクトの値を取得する際に利用できるgetメソッドを使って新たな処理を追加します。


let user = {
  firstName: "John"
}

let proxy_user = new Proxy(user,{
  get(target,prop){
    console.log(target +'の' + prop +'へのアクセスに追加の処理を加えました')
    return target[prop]
  }
})

console.log(proxy_user.firstName)

handler{}の中にgetメソッドを追加しています。getの引数にはtarget, propがありますがtargetはuser, propはproxy_user.firstNameで指定したfirstNameに対応します。

Proxyを経由したアクセスは先ほどと同じでproxy_user.firstNameと記述します。handlerの中にgetメソッドを追加するとproxy_user.firstNameでfirstNameプロパティにアクセスする際に自動的にgetメソッドが実行され下記のメッセージが表示されます。


[object Object]のfirstNameへのアクセスに追加の処理を加えました
John

Proxyを経由してuserオブジェクトのnameにアクセスすることでコンソールにメッセージを表示するという追加の処理を加えることができました。

setメソッドを使って処理の追加

通常のオブジェクトへの値の設定方法を確認しておきます。userオブジェクトにlastNameプロパティを追加してその値をDoeとしています。


let user = {
  firstName: "John"
}

user.lastName = 'Doe';

console.log(user.lastName)

これをsetメソッドを利用して設定します。proxy_userを経由して設定したlastNameが元のuserオブジェクトにも反映されていることがわかります。


let user = {
  firstName: "John",
};

let proxy_user = new Proxy(user, {});

proxy_user.lastName = "Doe";

console.log(proxy_user.lastName);

console.log(user.lastName);

//結果
Doe // proxy_user.lastName
Doe // user.lastName

次にhandlerにsetメソッドを追加します。


let proxy_user = new Proxy(user,{
  get(target,prop){
    console.log(target +'の' + prop +'へのアクセスに追加の処理を加えました')
    return target[prop]
  },
  set(target,prop,value){
    console.log(target + 'に' + '新たにプロパティ' + prop + 'と値' + value + '追加')
    target[prop] = value
  }
})

setでは引数にtargetとpropだけではなくvalueも必要です。 targetはuser, propはlastNameでvalueはDoeに対応します。


proxy_user.lastName = 'Doe';

console.log(proxy_user.lastName)

実行するとsetメソッドのconsole.logの結果とproxy_user.lastNameでgetメソッドが実行されるため下記のメッセージが表示されます。


[object Object]に新たにプロパティlastNameと値Doe追加
[object Object]のlastNameへのアクセスに追加の処理を加えました
Doe

またproxy_userにforループを設定して展開を行うこともできます。


let user = {
  firstName: "John",
};

let proxy_user = new Proxy(user, {
  get(target, prop) {
    console.log(target + "の" + prop + "へのアクセスに追加の処理を加えました");
    return target[prop];
  },
  set(target, prop, value) {
    console.log(
      target + "に" + "新たにプロパティ" + prop + "と値" + value + "追加"
    );
    target[prop] = value;
  },
});

proxy_user.lastName = "Doe";

for (key in proxy_user) {
  console.log(proxy_user[key]);
}

 実行すると下記のようにforループの結果が表示されます。


[object Object]に新たにプロパティlastNameと値Doe追加
[object Object]のfirstNameへのアクセスに追加の処理を加えました
John
[object Object]のlastNameへのアクセスに追加の処理を加えました
Doe

ここまでの説明でProxyを経由させることで新たな処理を追加することや新たなプロパティを追加できることがわかりました。

deletePropertyメソッドの使い方

オブジェクトのプロパティを削除する際に利用することができるメソッドがdeletePropertyメソッドです。新たに追加したmiddlenameプロパティを削除します。


let user = {
  firstName: "John",
  middleName: "M",
};

let proxy_user = new Proxy(user, {
  get(target, prop) {
    console.log(target + "の" + prop + "へのアクセスに追加の処理を加えました");
    return target[prop];
  },
  set(target, prop, value) {
    console.log(
      target + "に" + "新たにプロパティ" + prop + "と値" + value + "追加"
    );
    target[prop] = value;
  },
  deleteProperty(target, prop) {
    console.log(target + "から" + "プロパティ" + prop + "を削除");
    delete target[prop];
  },
});

console.log(proxy_user.middleName);
delete proxy_user.middleName;
console.log(proxy_user.middleName);

実行すると”[object Object]からプロパティmiddleNameを削除”のメッセージが表示されます。 削除後にプロパティにアクセスしているのでundefinedと表示されます。


[object Object]のmiddleNameへのアクセスに追加の処理を加えました
M
[object Object]からプロパティmiddleNameを削除
[object Object]のmiddleNameへのアクセスに追加の処理を加えました
undefined

Reflectの使い方

Reflectを利用すると下記の方法でオブジェクトのプロパティの値を取得することができます。Reflectを利用する際にnewなどを利用してインスタンスを作成する必要はありません。


let user = {
  firstName: "John",
  lastName: "Doe"
}

console.log(Reflect.get(user,'firstName')) //John

またsetメソッドで新たにプロパティを追加することもできます。


let user = {
  firstName: "John",
  lastName: "Doe",
};

Reflect.set(user, "middleName", "m");

console.log(Reflect.get(user, "middleName")); //m

ReflectとProxyを一緒に利用する

Reflectを利用することで先ほどのgetメソッドやsetメソッドを書き換えることができます。target[prop]をReflect.get(target,prop.receiver)に置き換えています。


let user = {
  firstName: "John",
  lastName: "Doe"
}

let proxy_user = new Proxy(user,{
  get(target,prop,receiver){
    return Reflect.get(target,prop,receiver)
  },
})

console.log(proxy_user.firstName)

getメソッドの第3引数にreceiverが追加されていますが、receiverはproxyそのものでconsole.log(receiver)で中身を確認することができます。


let user = {
  firstName: "John",
  lastName: "Doe"
}

let proxy_user = new Proxy(user,{
  get(target,prop,receiver){
    console.log(receiver) //receiverの中身を確認するために追加
    return Reflect.get(target,prop,receiver)
  },
})

console.log(proxy_user.firstName)

実行するとreceiverの中身を確認できます。


{ firstName: 'John', lastName: 'Doe' }

Reflect.getの引数にはargumentsを利用して下記のように設定することもできます。argumentにはtarget, prop, receiverが含まれています。


let user = {
  firstName: 'John',
  lastName: 'Doe',
};

let proxy_user = new Proxy(user, {
  get(target, prop, receiver) {
    return Reflect.get(...arguments);
  },
});

console.log(proxy_user.firstName);

setメソッドの中身はReflectを利用すると下記のように変更を行うことができます。


  set(target,prop,value,receiver){
    return Reflect.set(target,prop,value,receiver)
  },

Reflect.get

Reflect.getではオブジェクトだけではなく配列も指定することができます。

第二引数に1を指定することで配列番号の1を持つ要素を取得することができます。


const users = ['John', 'Jane', 'Kevin'];

console.log(Reflect.get(users, 1));//Jane

Reflect.set

オブジェクトのプロパティを追加したい場合にはsetを利用することができます。


const user = {
  firstName: 'John',
};

Reflect.set(user, 'lastName', 'John');

console.log(user);
//結果
{ firstName: 'John', lastName: 'John' }

Reflect.deleteProperty

オブジェクトからプロパティを削除する際に利用することができます。


const user = {
  firstName: 'John',
  lastName: 'Doe',
};

Reflect.deleteProperty(user, 'lastName');

console.log(user);
//結果
{ firstName: 'John' }