Michi's Tech Blog

一人前のWebエンジニアを目指して

【JavaScript】Proxyってなんすか?

こんにちは!
スマレジ テックファームのMichiです!

突然ですが、Vue.jsのコードでreactiveなオブジェクトをconsole.logしたらこんな値が返ってきます。

「このProxyってやつ、なんすか?」とずっと思っていたので、調べてみることにしました!

Proxyとは?

公式ドキュメントでは ...

MDNのドキュメントにはこのように記載があります。

Proxy オブジェクトにより別なオブジェクトのプロキシを作成することができ、そのオブジェクトの基本的な操作を傍受したり再定義したりすることができます。

Proxy - JavaScript | MDN

「なんのこっちゃ???」って感じですよね。これで理解できるなら苦労せんて。
というわけで、もう少し嚙み砕いて説明してみます。

ひと言で説明すると...

Proxyとは「元のオブジェクトの動作をカスタマイズするためのオブジェクト」です。
例を見てみましょう。

// 元のオブジェクト
const data = { red: '赤色', yellow: '黄色'}

// Proxyで元のオブジェクトをカスタマイズする
const proxy = new Proxy(data, {
  get(target, prop) {
    return 'hello'
  }
})

console.log(data.red) // 結果:赤色
console.log(proxy.red) // 結果:hello

元のオブジェクトdata.redには赤色という値が格納されているにもかかわらず、Proxyオブジェクトでラップしたproxy.redを呼び出すと、元のオブジェクトには存在しないhelloという文字列が返ってきます。これが上で書いた「元のオブジェクトの動作をカスタマイズする」ということです。
ちなみに、Proxyは直訳すると「代理」という意味になります。元のオブジェクトの代わりに、処理を「代理」で行ってくれているというわけですね。

使い方

基本構文

new Proxy(target, handler)

引数

  1. target: 動作をカスタマイズされる側のオブジェクト
  2. handler: ターゲットの動作を定義するためのオブジェクト

Proxyオブジェクトはnew演算子で初期化し呼び出します。第1引数にターゲット、 第2引数にはハンドラーを取ります。ハンドラーで定義できるメソッドは決まっており、具体的には以下の表の通りとなります。

引用:https://ja.javascript.info/proxy

このハンドラーで定義できるメソッドのことをトラップと呼称したりもします。今回はこの中でも代表的なトラップである、getsetについて見ていきます。

getトラップ

最初の例でも出てきましたが、getはプロパティが取得されたときに呼び出されるトラップです。

基本構文

get(target, prop, receiver) {
  // プロパティを取得したときに実行する処理
}

引数

  1. target: 元のオブジェクト(new Proxyの第一引数と同じ)
  2. prop: オブジェクトのプロパティ名
  3. receiver: proxyオブジェクト自身(今回は使わない)

戻り値

  • 任意の値

具体例
以下は、存在しないプロパティを取得しようとした際エラーとなる実装です。

const data = { red: "赤色", yellow: "黄色" }

const proxy = new Proxy(data, {
  get(target, prop) {
    if (prop in target) {
      return target.prop
    } else {
      throw new Error("存在しないプロパティです")
    }
  }
})

console.log(proxy.blue) // 結果:存在しないプロパティです

setトラップ

setはプロパティを設定するときに呼び出されるトラップです。

基本構文

set(target, prop, value, receiver) {
  // プロパティを設定するときに実行する処理
}

引数

  1. target: 元のオブジェクト(new Proxyの第一引数と同じ)
  2. prop: オブジェクトのプロパティ名
  3. value: 新たに設定するプロパティの値
  4. receiver: proxyオブジェクト自身(今回は使わない)

戻り値

  • 真偽値(設定が成功するとtrue、失敗するとfalseを返す)

具体例
以下は、文字列以外をプロパティに設定しようとした際エラーとなる実装です。

const data = { red: "赤色", yellow: "黄色" }

const proxy = new Proxy(data, {
  set(target, prop, value) {
    if (typeof value !== "string") {
      throw new Error("設定できるのは文字列のみです")
      return false
    } else {
      target.prop = value
      return true
    }
  }
})

proxy.red = 1 // 結果:設定できるのは文字列のみです

他にも様々なトラップが存在します。気になる方は、公式ドキュメントなどで調べてみるとよいでしょう。

ユースケース

Proxyを使ったユースケースには、様々なものが考えられます。以下はその例です。

  • デフォルト値の取得 ... 存在しないオブジェクトのプロパティを取得しようとした時にデフォルトの値を返す。
  • 大文字⇔小文字の変換 ... プロパティの取得・設定時に大文字/小文字のどちらか一方へ変換することによって、大文字/小文字の区別をなくす。
  • バリデーション ... プロパティの設定時にバリデーションを設定して、不正な値をはじく。

とは言いつつ、実際に自分でProxyオブジェクトを作成する機会はあまりないでしょう。一番多いのは、外部ライブラリやフレームワークの中で使用されているパターンです。
冒頭でも書いたように、Vue.jsのreactiveオブジェクトをconsole.logするとProxyオブジェクトが返されます。これは、 Vue.js におけるリアクティブ性は Proxy オブジェクトにより実現されているからです。

まとめ

JavaScriptのProxyについて解説しました。
自分でProxyを使う機会はなかなかないかもしれませんが、フレームワークからProxyが返ってきたときに「なにこれ!?」ってならないように、勉強していきたいですね。