情報アイランド

「情報を制する者は世界を制す」をモットーに様々な情報を提供することを目指すブログです。現在はプログラミング関連情報が多めですが、投資関連情報も取り扱っていきたいです。

ECMAScript 6の新機能(24)プロキシ

ECMAScript 6でプロキシという新しい概念が導入されました。

プロキシとは

プロキシとは任意のオブジェクトをラップし、オブジェクトを直接操作するのではなくプロキシを経由して操作した場合にはオブジェクト本来の動作とは異なる動作を行うものです。

プロキシがラップするオブジェクトをターゲットと言い、オブジェクト本来の動作とは異なる動作を提供するオブジェクトをハンドラと言います。

プロキシはターゲットとハンドラを結び付けるものでもあります。

また、無効にすることができるプロキシを作成することもできます。プロキシを無効にすると以後そのプロキシを経由してターゲットを操作することができなくなります(操作しようとすると例外が発生するようになります)。

プロキシはProxyクラスとして提供されています。

プロキシの作成

プロキシを作成するには主に2つの方法があります。

1つは普通のプロキシを作成する方法であり、もう1つは無効にすることができるプロキシを作成する方法です。

普通のプロキシの作成

普通のプロキシを作成するにはProxyクラスのコンストラクタを使用します。

第1引数にターゲットを指定します。

第2引数にハンドラを指定します。

返り値としてプロキシが得られます。

なお、ハンドラとして空のオブジェクトを指定すると、プロキシを経由してターゲットを操作しようとした場合に何もしないでただそのままターゲットに操作の実行を任せるだけのプロキシが作成されます。

したがって、ターゲットを直接操作するのもプロキシを経由してターゲットを操作するのも同じ結果となります。

C:\work\node>node
> var target = { x: 100, y: 200, z: 500 }
undefined
> var proxy = new Proxy(target, {})
undefined
> target.x
100
> proxy.x
100
> target.y
200
> proxy.y = 300
300
> target.y
300
> target.z
500
> delete proxy.z
true
> target.z
undefined

無効にすることができるプロキシの作成

無効にすることができるプロキシを作成するにはProxy.revocable関数を使用します。

第1引数にターゲットを指定します。

第2引数にハンドラを指定します。

返り値としてオブジェクトが得られます。

このオブジェクトのproxyプロパティに無効にすることができるプロキシが格納されており、revokeプロパティにプロキシを無効にするための関数が格納されています。

この関数を呼び出すとプロキシが無効化され、以後そのプロキシを経由してターゲットを操作しようとすると例外が発生するようになります。

> var o = Proxy.revocable(target, {})
undefined
> o
{ proxy: { x: 100, y: 300 }, revoke: [Function] }
> target.y
300
> o.proxy.y
300
> o.proxy.y = 400
400
> target.y
400
> o.revoke()
undefined
> target.y
400
> o.proxy.y
TypeError: Cannot perform 'get' on a proxy that has been revoked
    at repl:1:8
    at REPLServer.defaultEval (repl.js:272:27)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:441:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:188:7)
    at REPLServer.Interface._onLine (readline.js:224:10)
    at REPLServer.Interface._line (readline.js:566:8)
    at REPLServer.Interface._ttyWrite (readline.js:843:14)
> o.proxy.y = 500
TypeError: Cannot perform 'set' on a proxy that has been revoked
    at repl:1:11
    at REPLServer.defaultEval (repl.js:272:27)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:441:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:188:7)
    at REPLServer.Interface._onLine (readline.js:224:10)
    at REPLServer.Interface._line (readline.js:566:8)
    at REPLServer.Interface._ttyWrite (readline.js:843:14)
> target.y
400

ハンドラの実装

ハンドラにはターゲット本来の動作とは異なる動作を動作毎にメソッドとして提供するようにします。

たとえば、プロパティを取得するという動作を提供するにはgetメソッドを実装します。

下ではxプロパティの取得が行われた際にターゲット本来のxプロパティの値の代わりにfooという値を返す動作を実装しています。

> var handler = {
... get: function (target, propKey, receiver) {
..... if (propKey === 'x') {
....... return 'foo';
....... }
..... else {
....... return target[propKey];
....... }
..... }
... }
undefined
> var proxy3 = new Proxy(target, handler)
undefined
> target.x
100
> proxy3.x
'foo'
> target.y
400
> proxy3.y
400

このようにして、プロキシを経由してターゲットを操作しようとした場合にのみターゲット本来の動作とは異なる動作を行わせることができます。

ハンドラで実装できる操作には下のようなものがあります。

なお、targetはターゲットを表し、proxyはプロキシを表します。

オブジェクトに対する操作

  • defineProperty(target, propKey, propDesc)・・・Object.defineProperty(proxy, propKey, propDesc)の動作を実装します。返り値として操作が成功したかを真偽値として返します。
  • deleteProperty(target, propKey)・・・delete proxy[propKey]の動作を実装します。返り値として操作が成功したかを真偽値として返します。
  • get(target, propKey, proxy)・・・proxy[propKey]の動作を実装します。
  • getOwnPropertyDescriptor(target, propKey)・・・Object.getOwnPropertyDescriptor(proxy, propKey)の動作を実装します。
  • getPrototypeOf(target)・・・Object.getPrototypeOf(proxy)の動作を実装します。
  • has(target, propKey)・・・propKey in proxyの動作を実装します。
  • isExtensible(target)・・・Object.isExtensible(proxy)の動作を実装します。
  • ownKeys(target)・・・全てのプロパティのキーから成る配列を返す動作を実装します。Object.getOwnPropertyPropertyNames(proxy)Object.getOwnPropertyPropertySymbols(proxy)Object.keys(proxy)の操作において使用されます。
  • preventExtensions(target)・・・Object.preventExtensions(proxy)の動作を実装します。返り値として操作が成功したかを真偽値として返します。
  • set(target, propKey, value, proxy)・・・proxy[propKey] = valueの動作を実装します。返り値として操作が成功したかを真偽値として返します。
  • setPrototypeOf(target, proto)・・・Object.setPrototypeOf(proxy, proto)の動作を実装します。返り値として操作が成功したかを真偽値として返します。

関数に対する操作

  • apply(target, thisArgument, argumentsList)・・・proxy.apply(thisArgument, argumentsList)proxy.call(thisArgument, ...argumentsList)proxy(...argumentsList)の動作を実装します。
  • construct(target, argumentsList, newTarget)・・・new proxy(...argumentsList)の動作を実装します。ただし、newTargetnew target(...argumentsList)により生成されるオブジェクトです。

ハンドラの実装における制約

ハンドラのメソッドの実装においては本来の動作との整合性をある程度保つために制約が課されていることがあります。

そして、制約に違反するような実装を行っているメソッドが呼び出された場合にはTypeErrorが発生します。

たとえば、constructメソッドにはオブジェクトを返さなければならないという制約が課されています。そのため、返り値としてnullを返すconstructメソッドの実装が呼び出された場合にはTypeErrorが発生します。

> var handler2 = {
... construct: function (target, argumentsList, newTarget) {
..... return null;
..... }
... }
undefined
> var f = function () {}
undefined
> var proxy4 = new Proxy(f, handler2)
undefined
> new f()
{}
> new proxy4()
TypeError: 'construct' on proxy: trap returned non-object ('null')
    at repl:1:1
    at REPLServer.defaultEval (repl.js:272:27)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:441:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:188:7)
    at REPLServer.Interface._onLine (readline.js:224:10)
    at REPLServer.Interface._line (readline.js:566:8)
    at REPLServer.Interface._ttyWrite (readline.js:843:14)

Reflect

Reflectオブジェクトはオブジェクト本来の動作を動作毎にメソッドとして提供しています。メソッド名はハンドラにおけるものと同一です。

> Reflect.get(target, 'x', target)
100
> Reflect.getPrototypeOf(f)
[Function]
> Reflect.has(target, 'x')
true

プロトタイプ

プロキシを他のオブジェクトのプロトタイプにすることもできます。

C:\work\node>node
> var ptarget = { a: 100 }
undefined
> var target = Object.create(ptarget)
undefined
> target.b = 200
200
> var handler = {
... get: function (target, propKey, receiver) {
..... if (propKey === 'c') {
....... return 300;
....... }
..... else {
....... return target[propKey];
....... }
..... }
... }
undefined
> var proxy = new Proxy(target, handler)
undefined
> var obj = Object.create(proxy)
undefined
> obj.d = 400
400
> obj.d
400
> obj.c
300
> obj.b
200
> obj.a
100
pizyumi
プログラミング歴19年のベテランプログラマー。業務システム全般何でも作れます。現在はWeb系の技術を勉強中。
スポンサーリンク

-ecmascript 2015, ecmascript 6, Javascript