この翻訳は不完全です。英語から この記事を翻訳 してください。

Proxy オブジェクトは、基本的な操作 (例えばプロパティの検索、代入、列挙、関数の起動など) について独自の動作を定義するために使用します。

用語

ハンドラ
トラップを含むプレースホルダオブジェクト。
トラップ
プロパティへのアクセスを提供するメソッド。これは OS におけるトラップのコンセプトに似たものです。
ターゲット
Proxy が仮想化するオブジェクト。たいていは Proxy のストレージバックエンドとして使用されます。オブジェクトの拡張や設定を禁止するプロパティに関する (変化していないという意味での) 不変条件は、このターゲットについて検証されます。

構文

var p = new Proxy(target, handler);

引数

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

メソッド

Proxy.revocable()
取り消し可能な Proxy オブジェクトを生成します。

handler オブジェクトのメソッド

handler オブジェクトは、Proxy のトラップを含むプレースホルダオブジェクトです。

すべてのトラップはオプションです。トラップが定義されていない場合、デフォルトの振る舞いはターゲットに操作を転送することです。

handler.getPrototypeOf()
Object.getPrototypeOf に対するトラップです。
handler.setPrototypeOf()
Object.setPrototypeOf に対するトラップです。
handler.isExtensible()
Object.isExtensible に対するトラップです。
handler.preventExtensions()
Object.preventExtensions に対するトラップです。
handler.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor に対するトラップです。
handler.defineProperty()
Object.defineProperty に対するトラップです。
handler.has()
in 操作に対するトラップです。
handler.get()
プロパティ値を取得するためのトラップです。
handler.set()
プロパティ値を設定するためのトラップです。
handler.deleteProperty()
delete 操作に対するトラップです。
handler.ownKeys()
Object.getOwnPropertyNamesObject.getOwnPropertySymbols に対するトラップです。
handler.apply()
関数呼び出しに対するトラップです。
handler.construct()
new 操作に対するトラップです。

いくつかの非標準のトラップは 廃止され取り除かれました

非常に簡単な例

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

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 が出力されます。操作は正しく転送されました

 上記のコードはJavaScriptオブジェクトでは動作しますが、DOM要素などのネイティブブラウザオブジェクトでは動作しないことに注意してください。See this for one solution.

バリデーション

Proxy を使うと、オブジェクトに渡された値を簡単に検証できます。この例では set ハンドラを使用しています。

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('年齢が整数ではありません');
      }
      if (value > 200) {
        throw new RangeError('年齢が不正なようです');
      }
    }

    // 値を保存する既定の挙動
    obj[prop] = value;

    // 値の保存が成功したことを返します。
    return true;
  }
};

let person = new Proxy({}, validator);

person.age = 100;
console.log(person.age); // 100
person.age = 'young'; // 例外が投げられる
person.age = 300; // 例外が投げられる

コンストラクタを拡張する

関数の Proxy で、コンストラクタを新たなコンストラクタへ簡単に拡張できます。この例では construct および apply ハンドラを使用しています。

function extend(sup,base) {
  var descriptor = Object.getOwnPropertyDescriptor(
    base.prototype,"constructor"
  );
  base.prototype = Object.create(sup.prototype);
  var handler = {
    construct: function(target, args) {
      var obj = Object.create(base.prototype);
      this.apply(target,obj,args);
      return obj;
    },
    apply: function(target, that, args) {
      sup.apply(that,args);
      base.apply(that,args);
    }
  };
  var proxy = new Proxy(base,handler);
  descriptor.value = proxy;
  Object.defineProperty(base.prototype, "constructor", descriptor);
  return proxy;
}

var Person = function(name){
  this.name = name;
};

var Boy = extend(Person, function(name, age) {
  this.age = age;
});

Boy.prototype.sex = "M";

var Peter = new Boy("Peter", 13);
console.log(Peter.sex);  // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age);  // 13

DOM ノードの操作

2 つの異なる要素の属性やクラス名を切り替えたい場合があります。それを実現する方法を紹介しましょう。

let view = new Proxy({
  selected: null
},
{
  set: function(obj, prop, newval) {
    let oldval = obj[prop];

    if (prop === 'selected') {
      if (oldval) {
        oldval.setAttribute('aria-selected', 'false');
      }
      if (newval) {
        newval.setAttribute('aria-selected', 'true');
      }
    }

    // 値を保存する既定の挙動
    obj[prop] = newval;

    // 値の保存が成功したことを返します。
    return true;
  }
});

let i1 = view.selected = document.getElementById('item-1');
console.log(i1.getAttribute('aria-selected')); // 'true'

let i2 = view.selected = document.getElementById('item-2');
console.log(i1.getAttribute('aria-selected')); // 'false'
console.log(i2.getAttribute('aria-selected')); // 'true'

値補正と追加プロパティ

この products プロキシオブジェクトは、渡された値を評価し、必要であれば配列に変換します。また、latestBrowser という追加プロパティをゲッターとセッターの両方でサポートしています。

let products = new Proxy({
  browsers: ['Internet Explorer', 'Netscape']
},
{
  get: function(obj, prop) {
    // 追加プロパティ
    if (prop === 'latestBrowser') {
      return obj.browsers[obj.browsers.length - 1];
    }

    // 値を返す既定の挙動
    return obj[prop];
  },
  set: function(obj, prop, value) {
    // 追加プロパティ
    if (prop === 'latestBrowser') {
      obj.browsers.push(value);
      return;
    }

    // 値が配列でなければ変換
    if (typeof value === 'string') {
      value = [value];
    }

    // 値を保存する既定の挙動
    obj[prop] = value;

    // 値の保存が成功したことを返します。
    return true;
  }
});

console.log(products.browsers); // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox'; // (間違えて) 文字列を渡す
console.log(products.browsers); // ['Firefox'] <- 問題ありません、値は配列になっています

products.latestBrowser = 'Chrome';
console.log(products.browsers); // ['Firefox', 'Chrome']
console.log(products.latestBrowser); // 'Chrome'

配列項目のオブジェクトをそのプロパティから検索

このプロキシは配列をいくつかの実用機能で拡張しています。見ての通り、Object.defineProperties を使わなくても柔軟にプロパティを「定義」できます。この例は、テーブルの列をそのセルから検索するようなコードに応用できます。その場合、ターゲットは table.rows となります。

let products = new Proxy([
  { name: 'Firefox', type: 'browser' },
  { name: 'SeaMonkey', type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
],
{
  get: function(obj, prop) {
    // 値を返す既定の挙動、prop は通常整数値
    if (prop in obj) {
      return obj[prop];
    }

    // 製品の数を取得、products.length のエイリアス
    if (prop === 'number') {
      return obj.length;
    }

    let result, types = {};

    for (let product of obj) {
      if (product.name === prop) {
        result = product;
      }
      if (types[product.type]) {
        types[product.type].push(product);
      } else {
        types[product.type] = [product];
      }
    }

    // 製品を名前で取得
    if (result) {
      return result;
    }

    // 製品を種類で取得
    if (prop in types) {
      return types[prop];
    }

    // 製品の種類を取得
    if (prop === 'types') {
      return Object.keys(types);
    }

    return undefined;
  }
});

console.log(products[0]); // { name: 'Firefox', type: 'browser' }
console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' }
console.log(products['Chrome']); // undefined
console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
console.log(products.types); // ['browser', 'mailer']
console.log(products.number); // 3

完全な traps リストの例

教育用に traps リストの完全なサンプルを作成するため、そのような操作が特に適している非ネイティブオブジェクトを Proxy 化します。document.cookie のページにある "リトルフレームワーク" で生成される docCookies グローバルオブジェクトです。

/*
  var docCookies = ... get the "docCookies" object here:  
  https://developer.mozilla.org/ja/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
*/

var docCookies = new Proxy(docCookies, {
  get: function (oTarget, sKey) {
    return oTarget[sKey] || oTarget.getItem(sKey) || undefined;
  },
  set: function (oTarget, sKey, vValue) {
    if (sKey in oTarget) { return false; }
    return oTarget.setItem(sKey, vValue);
  },
  deleteProperty: function (oTarget, sKey) {
    if (sKey in oTarget) { return false; }
    return oTarget.removeItem(sKey);
  },
  enumerate: function (oTarget, sKey) {
    return oTarget.keys();
  },
  ownKeys: function (oTarget, sKey) {
    return oTarget.keys();
  },
  has: function (oTarget, sKey) {
    return sKey in oTarget || oTarget.hasItem(sKey);
  },
  defineProperty: function (oTarget, sKey, oDesc) {
    if (oDesc && 'value' in oDesc) { oTarget.setItem(sKey, oDesc.value); }
    return oTarget;
  },
  getOwnPropertyDescriptor: function (oTarget, sKey) {
    var vValue = oTarget.getItem(sKey);
    return vValue ? {
      value: vValue,
      writable: true,
      enumerable: true,
      configurable: false
    } : undefined;
  },
});

/* Cookies test */

console.log(docCookies.my_cookie1 = 'First value');
console.log(docCookies.getItem('my_cookie1'));

docCookies.setItem('my_cookie1', 'Changed value');
console.log(docCookies.my_cookie1);

仕様

仕様書 策定状況 コメント
ECMAScript 2015 (6th Edition, ECMA-262)
Proxy の定義
標準 最初期の定義
ECMAScript 2016 (ECMA-262)
Proxy の定義
標準  
ECMAScript 2017 (ECMA-262)
Proxy の定義
標準  
ECMAScript Latest Draft (ECMA-262)
Proxy の定義
ドラフト  

ブラウザ実装状況

Update compatibility data on GitHub
デスクトップモバイルサーバー
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewAndroid 版 ChromeEdge MobileAndroid 版 FirefoxAndroid 版 OperaiOS 版 SafariSamsung InternetNode.js
基本対応Chrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
revocableChrome 完全対応 ありEdge 完全対応 ありFirefox 完全対応 34IE 未対応 なしOpera 完全対応 ありSafari 完全対応 10WebView Android 完全対応 ありChrome Android 完全対応 ありEdge Mobile 完全対応 ありFirefox Android 完全対応 34Opera Android 完全対応 ありSafari iOS 完全対応 10Samsung Internet Android 完全対応 ありnodejs 完全対応 6.0.0
handler.applyChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.constructChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.definePropertyChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.deletePropertyChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.enumerate
非推奨非標準
Chrome 未対応 なしEdge 未対応 なしFirefox 未対応 37 — 47IE 未対応 なしOpera 未対応 なしSafari 未対応 なしWebView Android 未対応 なしChrome Android 未対応 なしEdge Mobile 未対応 なしFirefox Android 未対応 37 — 47Opera Android 未対応 なしSafari iOS 未対応 なしSamsung Internet Android 未対応 なしnodejs 未対応 なし
handler.getChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.getOwnPropertyDescriptorChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.getPrototypeOfChrome 未対応 なしEdge 未対応 なしFirefox 完全対応 49IE 未対応 なしOpera 未対応 なしSafari 未対応 なしWebView Android 未対応 なしChrome Android 未対応 なしEdge Mobile 未対応 なしFirefox Android 完全対応 49Opera Android 未対応 なしSafari iOS 未対応 なしSamsung Internet Android 未対応 なしnodejs 完全対応 6.0.0
handler.hasChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.isExtensibleChrome ? Edge ? Firefox 完全対応 31IE 未対応 なしOpera ? Safari ? WebView Android ? Chrome Android ? Edge Mobile ? Firefox Android 完全対応 31Opera Android ? Safari iOS ? Samsung Internet Android ? nodejs 完全対応 6.0.0
handler.ownKeysChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18
補足
完全対応 18
補足
補足 In Firefox 42, the implementation got updated to reflect the final ES2015 specification: The result is now checked if it is an array and if the array elements are either of type string or of type symbol. Enumerating duplicate own property names is not a failure anymore.
IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18
補足
完全対応 18
補足
補足 In Firefox 42, the implementation got updated to reflect the final ES2015 specification: The result is now checked if it is an array and if the array elements are either of type string or of type symbol. Enumerating duplicate own property names is not a failure anymore.
Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.preventExtensionsChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 22IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 22Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.setChrome 完全対応 49Edge 完全対応 12Firefox 完全対応 18IE 未対応 なしOpera 完全対応 36Safari 完全対応 10WebView Android 完全対応 49Chrome Android 完全対応 49Edge Mobile 完全対応 ありFirefox Android 完全対応 18Opera Android 完全対応 36Safari iOS 完全対応 10Samsung Internet Android 完全対応 5.0nodejs 完全対応 6.0.0
handler.setPrototypeOfChrome ? Edge ? Firefox 完全対応 49IE 未対応 なしOpera ? Safari ? WebView Android ? Chrome Android ? Edge Mobile ? Firefox Android 完全対応 49Opera Android ? Safari iOS ? Samsung Internet Android ? nodejs 完全対応 6.0.0

凡例

完全対応  
完全対応
未対応  
未対応
実装状況不明  
実装状況不明
非標準。ブラウザー間の互換性が低い可能性があります。
非標準。ブラウザー間の互換性が低い可能性があります。
非推奨。新しいウェブサイトでは使用しないでください。
非推奨。新しいウェブサイトでは使用しないでください。
実装ノートを参照してください。
実装ノートを参照してください。

参考資料

ライセンスに関する注記

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

ドキュメントのタグと貢献者

このページの貢献者: segayuu, u_7cc, kdex, yyss, teoli, ethertank, kohei.yoshino
最終更新者: segayuu,