Proxy

  • リビジョンの URL スラッグ: Core_JavaScript_1.5_Reference/Global_Objects/Proxy
  • リビジョンのタイトル: Proxy
  • リビジョンの ID: 335051
  • 作成日:
  • 作成者: Yoshino
  • 現行リビジョン いいえ
  • コメント Added Japanese translation

このリビジョンの内容

{{ Non-standard_header() }}

{{ warning(" SpiderMonkey の Proxy 実装は試作段階であり、Proxy API と動作仕様はまだ安定したものではありません。SpiderMonkey の実装が最新の仕様書ドラフトを反映していないこともありえます。これは実験的な機能として提供されており、いつでも変更の可能性があります。現在の実装に依存して本番環境用のコードを書くことはお避けください。") }}

{{warning("このページは Firefox 18 で実装された (「ダイレクトプロキシ」と呼ばれる) 新しい API について解説しています。(Firefox 17 以下の) 従来の API については、旧 Proxy API ページを参照してください。")}}

はじめに

プロキシとは、開発者自身が JavaScript で動作を定義するオブジェクトです。オブジェクトの既定動作は JavaScript エンジンに実装されており、たいていは C++ などの低レベル言語で記述されています。プロキシは、オブジェクトの挙動のほとんどを JavaScript で定義できるようにします。これは メタプログラミング API を提供するものと言われています。

用語

キャッチオール機構 (あるいは「仲裁 API」)
この機能の専門用語です。
プロキシ
アクセスを傍受するオブジェクト。
ハンドラ
トラップを含むプレースホルダオブジェクト。
トラップ
プロパティへのアクセスを提供するメソッド。これは OS におけるトラップのコンセプトに似たものです。
ターゲット
プロキシが仮想化するオブジェクト。たいていはプロキシのストレージバックエンドとして使用されます。オブジェクトの拡張や設定を禁止するプロパティに関する不変条件は、このターゲットについて検証されます。

プロキシ API

プロキシは新しいオブジェクトであり、既存のオブジェクトを「プロキシ化」することはできません。プロキシは以下のようにして作成できます。

var p = new Proxy(target, handler);

ここでは、

  • target はオブジェクトです (ネイティブの配列、関数、あるいは他のプロキシも含め、どのような種類のオブジェクトでも構いません)。
  • handler は、関数をプロパティとして持つオブジェクトで、その関数で、プロキシに対して操作が行われた場合の挙動を定義します。

ハンドラ API

トラップはすべてオプションです。トラップが定義されてない場合、ターゲットへ操作は既定の挙動で行われます。

JavaScript コード ハンドラメソッド 意味
Object.getOwnPropertyDescriptor(proxy, name) getOwnPropertyDescriptor
function(target, name) -> PropertyDescriptor | undefined
妥当なプロパティディスクリプタオブジェクトを返します。そのエミュレートされたオブジェクトに name という名前のプロパティが存在しない場合は undefined を返します。
Object.getOwnPropertyNames(proxy) getOwnPropertyNames function(target) -> [String] そのエミュレートされたオブジェクトの、(継承していない) 独自プロパティ名をすべて含んだ配列を返します。
Object.defineProperty(proxy,name,pd) defineProperty function(target, name, propertyDescriptor) -> any 与えられた propertyDescriptor によって属性が決定される新しいプロパティを定義します。このメソッドの戻り値は無視されます。
delete proxy.name deleteProperty function(target, name) -> boolean 指名されたプロパティをプロキシから削除します。このメソッドの戻り値は真偽値を取り、name プロパティが正しく削除されたかどうかを示します。
Object.freeze(proxy) freeze function(target) -> boolean オブジェクトを凍結します。真偽値は、その操作が成功したかどうかを示します。
Object.seal(proxy) seal function(target) -> boolean オブジェクトを封印します。真偽値は、その操作が成功したかどうかを示します。
Object.preventExtensions(proxy) preventExtensions function(target) -> boolean オブジェクトの拡張を禁止します。真偽値は、その操作が成功したかどうかを示します。
name in proxy has function(target, name) -> boolean  
Object.prototype.hasOwnProperty.call(proxy, name) hasOwn function(target, name) -> boolean  

proxy.name (「値を取得する」コンテキストにおいて)

receiver.name (receiver がプロキシから継承したもので、name を上書きしていない場合)

get function(target, name, receiver) -> any receiver はプロキシもしくはプロキシから継承したオブジェクトです。

proxy.name = val (「値を設定する」コンテキストにおいて)

receiver.name = val (receiver がプロキシから継承したもので、name を上書きしていない場合)

set function(target, name, val, receiver) -> boolean receiver はプロキシもしくはプロキシから継承したオブジェクトです。
for(prop in proxy){...} enumerate function(target) -> [String] プロキシを使う開発者の視点で見た場合、for..in ループ内で現れるプロパティの順番は、戻り値の配列と同じになります。for..in ループには、列挙トラップであるべきところ反復トラップが呼び出されるという 既知のバグ があります。
for(prop of proxy){...} iterate function(target) -> iterator  
Object.keys(proxy) keys function(target) -> [String]  
proxy.apply(thisValue, args) apply function(target, thisValue, args) -> any  
new proxy(...args) construct function(target, args) -> any  

不変条件

プロキシは多くの機能を開発者に与えてくれるものの、言語の一貫性を保つため、一部の操作はトラップに掛からないようになっています。

  • 二重あるいは三重イコール演算子 (=====) はトラップに掛かりません。p1 === p2 は、p1p2 が同じプロキシを参照している場合のみ真となります。
  • Object.getPrototypeOf(proxy) は無条件に Object.getPrototypeOf(target) を返します。
  • typeof proxy は無条件に typeof target を返します。
  • Object.prototype.toString.call(proxy) は無条件に Object.prototype.toString.call(target) を返します。

非常に簡単な例

このプロキシは、与えられたプロパティ名がオブジェクトに存在しない場合、既定値である 37 を返します。

var handler = {
    get: function(target, name){
        return name in target?
            target[name] :
            37;
    }
};

var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37

何もしない転送プロキシ

この例では、プロキシが、それに対して適用されるすべての操作を転送する先に、ネイティブの JavaScript オブジェクトを使っています。

var target = {};
var p = new Proxy(target, {});

p.a = 37; // 操作はプロキシへ転送されます

console.log(target.a); // 37 が出力されます。操作は正しく転送されました

参考資料

ライセンスに関する注記

このページ内の一部のコンテンツ (テキストと例) は、CC 2.0 BY-NC-SA でコンテンツがライセンスされている ECMAScript wiki から引用あるいは改変して使用しています。

このリビジョンのソースコード

<p>{{ Non-standard_header() }}</p>
<p>{{ warning("
SpiderMonkey の Proxy 実装は試作段階であり、Proxy API と動作仕様はまだ安定したものではありません。SpiderMonkey の実装が最新の仕様書ドラフトを反映していないこともありえます。これは実験的な機能として提供されており、いつでも変更の可能性があります。現在の実装に依存して本番環境用のコードを書くことはお避けください。") }}</p>
<p>{{warning("このページは Firefox 18 で実装された (「ダイレクトプロキシ」と呼ばれる) 新しい API について解説しています。(Firefox 17 以下の) 従来の API については、旧 Proxy API ページを参照してください。")}}</p>
<h2 id="Introduction">はじめに</h2>
<p>プロキシとは、開発者自身が JavaScript で動作を定義するオブジェクトです。オブジェクトの既定動作は JavaScript エンジンに実装されており、たいていは C++ などの低レベル言語で記述されています。プロキシは、オブジェクトの挙動のほとんどを JavaScript で定義できるようにします。これは <strong>メタプログラミング API</strong> を提供するものと言われています。</p>
<h2 id="Terminology">用語</h2>
<dl>
  <dt>
    キャッチオール機構 (あるいは「仲裁 API」)</dt>
  <dd>
    この機能の専門用語です。</dd>
  <dt>
    プロキシ</dt>
  <dd>
    アクセスを傍受するオブジェクト。</dd>
  <dt>
    ハンドラ</dt>
  <dd>
    トラップを含むプレースホルダオブジェクト。</dd>
  <dt>
    トラップ</dt>
  <dd>
    プロパティへのアクセスを提供するメソッド。これは OS におけるトラップのコンセプトに似たものです。</dd>
  <dt>
    ターゲット</dt>
  <dd>
    プロキシが仮想化するオブジェクト。たいていはプロキシのストレージバックエンドとして使用されます。オブジェクトの拡張や設定を禁止するプロパティに関する不変条件は、このターゲットについて検証されます。</dd>
</dl>
<h2 id="Proxy_API">プロキシ API</h2>
<p>プロキシは新しいオブジェクトであり、既存のオブジェクトを「プロキシ化」することはできません。プロキシは以下のようにして作成できます。</p>
<pre class="brush: js">
var p = new Proxy(target, handler);
</pre>
<p>ここでは、</p>
<ul>
  <li><code>target</code> はオブジェクトです (ネイティブの配列、関数、あるいは他のプロキシも含め、どのような種類のオブジェクトでも構いません)。</li>
  <li><code>handler</code> は、関数をプロパティとして持つオブジェクトで、その関数で、プロキシに対して操作が行われた場合の挙動を定義します。</li>
</ul>
<h2 id="Handler_API">ハンドラ API</h2>
<p>トラップはすべてオプションです。トラップが定義されてない場合、ターゲットへ操作は既定の挙動で行われます。</p>
<table class="standard-table">
  <thead>
    <tr>
      <th>JavaScript コード</th>
      <th>ハンドラメソッド</th>
      <th>意味</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>Object.getOwnPropertyDescriptor(proxy, name)</code></td>
      <td><code><strong>getOwnPropertyDescriptor</strong><br />
        function(target, name) -&gt; PropertyDescriptor | undefined</code></td>
      <td>妥当なプロパティディスクリプタオブジェクトを返します。そのエミュレートされたオブジェクトに <code>name</code> という名前のプロパティが存在しない場合は <code>undefined</code> を返します。</td>
    </tr>
    <tr>
      <td><code>Object.getOwnPropertyNames(proxy)</code></td>
      <td><code><strong>getOwnPropertyNames</strong> function(target) -&gt; [String]</code></td>
      <td>そのエミュレートされたオブジェクトの、(継承していない) 独自プロパティ名をすべて含んだ配列を返します。</td>
    </tr>
    <tr>
      <td><code>Object.defineProperty(proxy,name,pd)</code></td>
      <td><code><strong>defineProperty</strong> function(target, name, propertyDescriptor) -&gt; any</code></td>
      <td>与えられた <code>propertyDescriptor</code> によって属性が決定される新しいプロパティを定義します。このメソッドの戻り値は無視されます。</td>
    </tr>
    <tr>
      <td><code>delete proxy.name</code></td>
      <td><code><strong>deleteProperty</strong> function(target, name) -&gt; boolean</code></td>
      <td>指名されたプロパティをプロキシから削除します。このメソッドの戻り値は真偽値を取り、<code>name</code> プロパティが正しく削除されたかどうかを示します。</td>
    </tr>
    <tr>
      <td><code>Object.freeze(proxy)</code></td>
      <td><code><strong>freeze</strong> function(target) -&gt; boolean</code></td>
      <td>オブジェクトを凍結します。真偽値は、その操作が成功したかどうかを示します。</td>
    </tr>
    <tr>
      <td><code>Object.seal(proxy)</code></td>
      <td><code><strong>seal</strong> function(target) -&gt; boolean</code></td>
      <td>オブジェクトを封印します。真偽値は、その操作が成功したかどうかを示します。</td>
    </tr>
    <tr>
      <td><code>Object.preventExtensions(proxy)</code></td>
      <td><code><strong>preventExtensions</strong> function(target) -&gt; boolean</code></td>
      <td>オブジェクトの拡張を禁止します。真偽値は、その操作が成功したかどうかを示します。</td>
    </tr>
    <tr>
      <td><code>name in proxy</code></td>
      <td><code><strong>has</strong> function(target, name) -&gt; boolean</code></td>
      <td>&nbsp;</td>
    </tr>
    <tr>
      <td><code>Object.prototype.hasOwnProperty.call(proxy, name)</code></td>
      <td><code><strong>hasOwn</strong> function(target, name) -&gt; boolean</code></td>
      <td>&nbsp;</td>
    </tr>
    <tr>
      <td>
        <p><code>proxy.name</code> (「値を取得する」コンテキストにおいて)</p>
        <p><code>receiver.name</code> (<code>receiver</code> がプロキシから継承したもので、<code>name</code> を上書きしていない場合)</p>
      </td>
      <td><code><strong>get</strong> function(target, name, receiver) -&gt; any</code></td>
      <td><code>receiver</code> はプロキシもしくはプロキシから継承したオブジェクトです。</td>
    </tr>
    <tr>
      <td>
        <p><code>proxy.name = val</code> (「値を設定する」コンテキストにおいて)</p>
        <p><code>receiver.name = val</code> (<code>receiver</code> がプロキシから継承したもので、<code>name</code> を上書きしていない場合)</p>
      </td>
      <td><code><strong>set</strong> function(target, name, val, receiver) -&gt; boolean</code></td>
      <td><code>receiver</code> はプロキシもしくはプロキシから継承したオブジェクトです。</td>
    </tr>
    <tr>
      <td><code>for(prop in proxy){...}</code></td>
      <td><code><strong>enumerate</strong> function(target) -&gt; [String]</code></td>
      <td>プロキシを使う開発者の視点で見た場合、<code>for..in</code> ループ内で現れるプロパティの順番は、戻り値の配列と同じになります。<code>for..in</code> ループには、列挙トラップであるべきところ反復トラップが呼び出されるという <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=783829">既知のバグ</a> があります。</td>
    </tr>
    <tr>
      <td><code>for(prop of proxy){...}</code></td>
      <td><code><strong>iterate</strong> function(target) -&gt; iterator</code></td>
      <td>&nbsp;</td>
    </tr>
    <tr>
      <td><code>Object.keys(proxy)</code></td>
      <td><code><strong>keys</strong> function(target) -&gt; [String]</code></td>
      <td>&nbsp;</td>
    </tr>
    <tr>
      <td><code>proxy.apply(thisValue, args)</code></td>
      <td><code><strong>apply</strong> function(target, thisValue, args) -&gt; any</code></td>
      <td>&nbsp;</td>
    </tr>
    <tr>
      <td><code>new proxy(...args)</code></td>
      <td><code><strong>construct</strong> function(target, args) -&gt; any</code></td>
      <td>&nbsp;</td>
    </tr>
  </tbody>
</table>
<h2 id="Invariants">不変条件</h2>
<p>プロキシは多くの機能を開発者に与えてくれるものの、言語の一貫性を保つため、一部の操作はトラップに掛からないようになっています。</p>
<ul>
  <li>二重あるいは三重イコール演算子 (<code>==</code>、<code>===</code>) はトラップに掛かりません。<code>p1 === p2</code> は、<code>p1</code> と <code>p2</code> が同じプロキシを参照している場合のみ真となります。</li>
  <li><code>Object.getPrototypeOf(proxy)</code> は無条件に <code>Object.getPrototypeOf(target)</code> を返します。</li>
  <li><code>typeof proxy</code> は無条件に <code>typeof target</code> を返します。</li>
  <li><code>Object.prototype.toString.call(proxy)</code> は無条件に <code>Object.prototype.toString.call(target)</code> を返します。</li>
</ul>
<h2 id="Examples">例</h2>
<h3 id="Very_simple_example">非常に簡単な例</h3>
<p>このプロキシは、与えられたプロパティ名がオブジェクトに存在しない場合、既定値である <code>37</code> を返します。</p>
<pre class="brush: js">
var handler = {
    get: function(target, name){
        return name in target?
            target[name] :
            37;
    }
};

var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
</pre>
<h3 id="No-op_forwarding_proxy">何もしない転送プロキシ</h3>
<p>この例では、プロキシが、それに対して適用されるすべての操作を転送する先に、ネイティブの JavaScript オブジェクトを使っています。</p>
<pre class="brush: js">
var target = {};
var p = new Proxy(target, {});

p.a = 37; // 操作はプロキシへ転送されます

console.log(target.a); // 37 が出力されます。操作は正しく転送されました

</pre>
<h2 id="See_also">参考資料</h2>
<ul>
  <li><a class="external" href="http://jsconf.eu/2010/speaker/be_proxy_objects.html">"Proxies are awesome" Brendan Eich の JSConf でのプレゼンテーション</a> (<a class="external" href="http://www.slideshare.net/BrendanEich/metaprog-5303821">スライド</a>)</li>
  <li><a class="external" href="http://wiki.ecmascript.org/doku.php?id=harmony:proxies">ECMAScript Harmony のプロキシ提案ページ</a> と <a class="external" href="http://wiki.ecmascript.org/doku.php?id=harmony:proxies_semantics">ECMAScript Harmony のプロキシ動作ページ</a></li>
  <li><a class="external" href="http://soft.vub.ac.be/~tvcutsem/proxies/">プロキシチュートリアル</a></li>
  <li><a href="/ja/docs/JavaScript/Old_Proxy_API">旧 Proxy API ページ</a></li>
</ul>
<h2 id="Licensing_note">ライセンスに関する注記</h2>
<p>このページ内の一部のコンテンツ (テキストと例) は、<a class="external" href="http://creativecommons.org/licenses/by-nc-sa/2.0/">CC 2.0 BY-NC-SA</a> でコンテンツがライセンスされている <a class="external" href="http://wiki.ecmascript.org/doku.php">ECMAScript wiki</a> から引用あるいは改変して使用しています。</p>
Revert to this revision