【JavaScript】Proxy オブジェクトを理解する

Proxy JavaScript

Proxy とは

Proxy オブジェクトは、ES6 から導入されたオブジェクトであり、Proxy オブジェクトを使うと、代理(proxy)となる別のオブジェクトを作成することができる。

代理のオブジェクトを経由して元のオブジェクトを操作できる仕組みが Proxy にはあるため、プロパティを操作する際に独自の処理を挟んで実行させることができる。

Proxy は次のような形で使用する。

// 元のオブジェクト
const target = {a: 10, b: 20, c: 30};

// proxy のプロパティを操作した際に実行させたい処理(トラップ)を定義したオブジェクト
const handler = {
  set: function(target, prop, value, receiver) {
    // proxy のプロパティに書き込みアクセスされたときの処理
  },
  get: function(target, prop, receiver) {
    // proxy のプロパティに読み出しアクセスされたときの処理
  },
  deleteProperty: function(target, prop) {
    // proxy のプロパティの削除が行なわれるときの処理
  },
  ...
}

const proxy = new Proxy(target, handler);

Proxy においては、操作対象となる元のオブジェクトをターゲット(target)、プロパティを操作した際に実行させたい処理を定義するオブジェクトをハンドラー(handler)、ハンドラーで定義されたメソッド(set, get, …etc)をトラップ(trap)と呼ぶ。

Proxy のトラップ

Proxy には様々な種類のトラップが用意されている。(参考:MDN

JavaScript エンジンには、その内部で使用している汎用的な関数(内部メソッド)が存在していて、コードからオブジェクトを操作する際には、その操作に対応した内部メソッドが JavaScript エンジンで実行されている。そしてそれらの内部メソッドに対して Proxy のトラップが用意されている。

JavaScript エンジンの内部メソッドと Proxy のトラップ、及びオブジェクト操作の関係は次のようになっている。

内部メソッドProxy のトラップ対応する
オブジェクトの操作
(これ以外の操作もある)
[[Call]]apply関数呼び出し
[[Construct]]constructnew F(...args)
[[DefineOwnProperty]]definePropertyObject.defineProperty(
obj, prop, descriptor)
[[Delete]]deletePropertydelete obj[prop]
[[Get]]getobj[prop]
[[GetOwnProperty]]getOwnPropertyDescriptorObject.getOwnPropertyDescriptor(
obj, prop)
[[GetPrototypeOf]]getPrototypeOfObject.getPrototypeOf(obj)
[[HasProperty]]hasprop in obj
[[IsExtensible]]isExtensibleObject.isExtensible(obj)
[[OwnPropertyKeys]]ownKeysObject.getOwnPropertyNames(obj)
.concat(Object.getOwnPropertySymbols(obj))
[[PreventExtensions]]preventExtensionsObject.preventExtensions(obj)
[[Set]]setobj[prop] = value
[[SetPrototypeOf]]setPrototypeOfObject.setPrototypeOf(obj, prototype)

Proxy のサンプル

基本的な Proxy の使い方は次のようになる。

const target = {a: 10, b: 20, c: 30};
const handler = {
  set: function(target, prop, value, receiver) {
    console.log(`set called: proxy.${prop} = ${value}`);
    target[prop] = value;
    return true;  // 成功したことを返す
  },
  get: function(target, prop, receiver) {
    console.log(`get called: proxy.${prop}`);
    return target[prop];
  },
  deleteProperty: function(target, prop) {
    console.log(`delete called: delete proxy.${prop}`);
    delete target[prop];
    return true;  // 成功したことを返す
  }
}

const proxy = new Proxy(target, handler);

proxy.a = 100;        // set called: proxy.a = 100
console.log(proxy);   // Proxy {a: 100, b: 20, c: 30}
console.log(target);  // {a: 100, b: 20, c: 30}

proxy.b;              // get called: proxy.b

delete proxy.c;       // delete called: delete proxy.c
console.log(proxy);   // Proxy {a: 100, b: 20}
console.log(target);  // {a: 100, b: 20}

次のように set トラップでエラーを投げるようにすると、全てのプロパティは変更不可となる。

const target = {a: 10, b: 20, c: 30};
const handler = {
  set: function(target, prop, value, receiver) {
    throw new Error('cannot change property');
  }
}

const proxy = new Proxy(target, handler);

proxy.a = 100;
// Uncaught Error: cannot change property

次のように get トラップを記述すると、自身で所有しないプロパティを参照されると ReferenceError を発生する。

const target = {a: 10, b: 20, c: 30};
const handler = {
  get: function(target, prop, receiver) {
    if (target.hasOwnProperty(prop)) {
      return target[prop];
    } else {
      throw new ReferenceError(`${prop} doesn't exist`);
    }
  }
}

const proxy = new Proxy(target, handler);

console.log(proxy.a);
// 10

console.log(proxy.d);
// Uncaught ReferenceError: d doesn't exist

タイトルとURLをコピーしました