CINXE.COM
<!DOCTYPE html><html lang="ja" data-theme-enabled="1"><head><script>window.currentUser = null;</script><script>window.shopCurrency = "EUR";</script><script>window.localCurrency = "JPY";</script><script>window.countryCode = "fi";</script><script>window.rateShopTo = {"JPY":161.72162693767189,"EUR":1,"USD":1.078956993853182,"AMD":421.5874435300883};</script><title itemprop="name">Proxy と Reflect</title><link href="/pack/styles.67c491fae690f87b1f9f.css" rel="stylesheet"><meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, minimum-scale=1.0"><meta name="apple-mobile-web-app-capable" content="yes"><!-- chrome autotranslate is enabled only for "en" main version--><meta name="google" content="notranslate"><script>if (window.devicePixelRatio > 1) document.cookie = 'pixelRatio=' + window.devicePixelRatio + ';path=/;expires=Tue, 19 Jan 2038 03:14:07 GMT';</script><link href="//fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic" rel="stylesheet"><link rel="apple-touch-icon-precomposed" href="/img/favicon/apple-touch-icon-precomposed.png"><link rel="canonical" href="https://ja.javascript.info/proxy"><meta name="msapplication-TileColor" content="#222A2C"><meta name="msapplication-TileImage" content="/img/favicon/tileicon.png"><link rel="icon" href="/img/favicon/favicon.png"><meta itemprop="image" content="https://ja.javascript.info/img/site_preview_en_512x512.png"><meta property="og:title" content="Proxy と Reflect"><meta property="og:image" content="https://ja.javascript.info/img/site_preview_en_1200x630.png"><meta property="og:image:type" content="image/png"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="fb:admins" content="100001562528165"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Proxy と Reflect"><meta name="twitter:site" content="@iliakan"><meta name="twitter:creator" content="@iliakan"><meta name="twitter:image" content="https://ja.javascript.info/img/site_preview_en_512x512.png"><meta name="google-adsense-account" content="ca-pub-6204518652652613"><link rel="prev" href="/js-misc"><link rel="next" href="/eval"><script data-collect-dnt="true" async src="https://scripts.simpleanalyticscdn.com/latest.js"></script><script>window.GA_ID = "UA-2056213-15";</script><script>window.YANDEX_METRIKA_ID = 32184394;</script><script>{function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-2LWB61WGYJ")}</script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-2LWB61WGYJ"></script><script>window.metrika={reachGoal:function(){}},window.yandex_metrika_callbacks=[function(){try{window.metrika=new Ya.Metrika({id:YANDEX_METRIKA_ID,webvisor:!0,clickmap:!0,params:{user:window.currentUser&&window.currentUser.id}}),metrika.trackLinks({delay:150}),window.addEventListener("error",function(r){window.metrika.reachGoal("JSERROR",{src:(r.filename||r.errorUrl)+": "+(r.lineno||r.errorLine),stack:r.stack||r.error&&r.error.stack,message:r.message})})}catch(r){}}];</script><script src="//mc.yandex.ru/metrika/watch.js" async></script><script>window.RECAPTCHA_ID = "6LfmLAEVAAAAAJMykMnf7aY8nkyTRmYi2ynx51R1";</script><script src="/pack/init.9d0331bb294e70c56db9.js"></script><script src="/pack/head.3c336e1939c677aeed65.js" defer></script><meta property="og:title" content="Proxy と Reflect"><meta property="og:type" content="article"><script src="/pack/tutorial.2da25d21c2b13cad643f.js" defer></script><script src="/pack/footer.4d7a62439a3935a4745e.js" defer></script></head><body class="no-icons"><script>window.fontTest();</script><div class="page-wrapper page-wrapper_sidebar_on"><!--[if IE]><div style="color:red;text-align:center">申し訳ありません、Internet Explorer はサポートしていないため新しいブラウザを使用してください。</div><![endif]--><div class="sitetoolbar sitetoolbar_tutorial"><script>window.langs = [{"code":"ar","name":"Arabic"},{"code":"az","name":"Azerbaijani"},{"code":"bg","name":"Bulgarian"},{"code":"bn","name":"Bengali"},{"code":"bs","name":"Bosnian"},{"code":"ca","name":"Catalan"},{"code":"cs","name":"Czech"},{"code":"da","name":"Danish"},{"code":"de","name":"German"},{"code":"el","name":"Greek"},{"code":"en","name":"English"},{"code":"es","name":"Spanish"},{"code":"fa","name":"Persian (Farsi)"},{"code":"fi","name":"Finnish"},{"code":"fr","name":"French"},{"code":"he","name":"Hebrew"},{"code":"hi","name":"Hindi"},{"code":"hr","name":"Croatian"},{"code":"hu","name":"Hungarian"},{"code":"hy","name":"Armenian"},{"code":"id","name":"Indonesian"},{"code":"it","name":"Italian"},{"code":"ja","name":"Japanese"},{"code":"ka","name":"Georgian"},{"code":"kk","name":"Kazakh"},{"code":"km","name":"Central Khmer"},{"code":"ko","name":"Korean"},{"code":"ku","name":"Kurdish"},{"code":"ky","name":"Kyrgyz"},{"code":"lt","name":"Lithuanian"},{"code":"me","name":"Montenegrin"},{"code":"ml","name":"Malayalam"},{"code":"ms","name":"Malay"},{"code":"my","name":"Burmese"},{"code":"nl","name":"Dutch"},{"code":"no","name":"Norvegian"},{"code":"pa","name":"Punjabi"},{"code":"pl","name":"Polish"},{"code":"pt","name":"Portuguese"},{"code":"ro","name":"Romanian"},{"code":"ru","name":"Russian"},{"code":"si","name":"Sinhala"},{"code":"sk","name":"Slovak"},{"code":"sl","name":"Slovenian"},{"code":"sq","name":"Albanian"},{"code":"sr","name":"Serbian"},{"code":"ta","name":"Tamil"},{"code":"te","name":"Telugu"},{"code":"test","name":"Test"},{"code":"th","name":"Thai"},{"code":"tk","name":"Turkmen"},{"code":"tr","name":"Turkish"},{"code":"ug","name":"Uyghur"},{"code":"uk","name":"Ukrainian"},{"code":"ur","name":"Urdu"},{"code":"uz","name":"Uzbek"},{"code":"v2","name":"v2"},{"code":"vi","name":"Vietnamese"},{"code":"zh-hant","name":"Chinese Traditional"},{"code":"zh","name":"Chinese"}];</script><script>window.lang = "ja";</script><div class="sitetoolbar__content"><div class="sitetoolbar__lang-switcher"><button class="sitetoolbar__dropdown-button" data-dropdown-toggler>JA</button><div class="sitetoolbar__dropdown-wrap"><div class="sitetoolbar__dropdown-body"><div class="sitetoolbar__lang-switcher-body"><div class="supported-langs supported-langs_toolbar"><div class="supported-langs__container"><ul class="supported-langs__list" style="height:200px"><li class="supported-langs__item"><a class="supported-langs__link" href="https://ar.javascript.info/proxy"><span class="supported-langs__brief">AR</span><span class="supported-langs__title">عربي</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://javascript.info/proxy"><span class="supported-langs__brief">EN</span><span class="supported-langs__title">English</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://es.javascript.info/proxy"><span class="supported-langs__brief">ES</span><span class="supported-langs__title">Español</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://fa.javascript.info/"><span class="supported-langs__brief">FA</span><span class="supported-langs__title">فارسی</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://fr.javascript.info/proxy"><span class="supported-langs__brief">FR</span><span class="supported-langs__title">Français</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://id.javascript.info/"><span class="supported-langs__brief">ID</span><span class="supported-langs__title">Indonesia</span></a></li></ul><ul class="supported-langs__list" style="height:200px"><li class="supported-langs__item"><a class="supported-langs__link" href="https://it.javascript.info/proxy"><span class="supported-langs__brief">IT</span><span class="supported-langs__title">Italiano</span></a></li><li class="supported-langs__item supported-langs__item_current"><a class="supported-langs__link" href="https://ja.javascript.info/proxy"><span class="supported-langs__brief">JA</span><span class="supported-langs__title">日本語</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://ko.javascript.info/proxy"><span class="supported-langs__brief">KO</span><span class="supported-langs__title">한국어</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://learn.javascript.ru/proxy"><span class="supported-langs__brief">RU</span><span class="supported-langs__title">Русский</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://tr.javascript.info/proxy"><span class="supported-langs__brief">TR</span><span class="supported-langs__title">Türkçe</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://uk.javascript.info/proxy"><span class="supported-langs__brief">UK</span><span class="supported-langs__title">Українська</span></a></li></ul><ul class="supported-langs__list" style="height:20px"><li class="supported-langs__item"><a class="supported-langs__link" href="https://zh.javascript.info/proxy"><span class="supported-langs__brief">ZH</span><span class="supported-langs__title">简体中文</span></a></li></ul></div><div class="supported-langs__text">私たちはこのオープンソースプロジェクトを世界中の人々に提供したいと考えています。このチュートリアルの内容をあなたが知っている言語に<a href="https://javascript.info/translate#help" rel="noopener noreferrer" target="_blank">翻訳するのを手伝って</a>ください。</div></div></div></div></div></div><div class="sitetoolbar__logo-wrap"><a class="sitetoolbar__link sitetoolbar__link_logo" href="/"><img class="sitetoolbar__logo sitetoolbar__logo_normal" src="/img/sitetoolbar__logo_en.svg" width="200" alt="" role="presentation"/><img class="sitetoolbar__logo sitetoolbar__logo_normal sitetoolbar__logo_dark" src="/img/sitetoolbar__logo_en-white.svg" width="200" alt="" role="presentation"/><img class="sitetoolbar__logo sitetoolbar__logo_small" src="/img/sitetoolbar__logo_small_en.svg" width="70" alt="" role="presentation"/><img class="sitetoolbar__logo sitetoolbar__logo_small sitetoolbar__logo_dark" src="/img/sitetoolbar__logo_small_en-white.svg" width="70" alt="" role="presentation"/><script>Array.prototype.forEach.call(document.querySelectorAll("img.sitetoolbar__logo"),function(e){let t=document.createElement("object");t.type="image/svg+xml",t.className=e.className,t.style.cssText="left:0;top:0;position:absolute",t.onload=function(){t.onload=null,e.style.visibility="hidden"},t.data=e.src,e.parentNode.insertBefore(t,e)});</script></a></div><div class="sitetoolbar__nav-toggle-wrap"><button class="sitetoolbar__nav-toggle" type="button"></button></div><nav class="sitetoolbar__sections"><ul class="sitetoolbar__sections-list"></ul></nav><div class="sitetoolbar__right-button-wrap"><a class="sitetoolbar-right-button sitetoolbar-right-button_courses" href="/ebook"><span class="sitetoolbar-right-button__extra-text">購入する</span>EPUB/PDF</a></div><div class="sitetoolbar__theme-switcher"><div class="theme-changer"><label class="theme-changer__label" for="theme-changer-input" data-tooltip="Change theme"><input class="theme-changer__input" type="checkbox" id="theme-changer-input" data-theme-changer="data-theme-changer"/><span class="theme-changer__icon theme-changer__icon_light-theme"></span><span class="theme-changer__icon theme-changer__icon_dark-theme"></span></label></div></div><div class="sitetoolbar__search-wrap"><div class="sitetoolbar__search-content"><button class="sitetoolbar__search-toggle" type="button"></button><form class="sitetoolbar__search" method="GET" action="/search"><div class="sitetoolbar__search-input"><div class="text-input"><input class="text-input__control" name="query" placeholder="チュートリアル内を検索" required="required" type="text"/></div><button class="sitetoolbar__find" type="submit">検索</button></div></form></div></div></div><div class="tablet-menu"><div class="tablet-menu__line"><div class="tablet-menu__content"><form class="tablet-menu-search" action="/search/"><input class="tablet-menu-search__input" type="search" name="query" placeholder="チュートリアル内を検索" required="required"/><button class="tablet-menu-search__button" type="submit" name="type" value="articles">検索</button></form></div></div><div class="tablet-menu__line"><div class="tablet-menu__content"><a class="map" href="/tutorial/map" data-action="tutorial-map"><span class="map__text">チュートリアルマップ</span></a></div></div><div class="tablet-menu__line"><div class="tablet-menu__content"><div class="theme-changer theme-changer_tablet-menu theme-changer_has-label"><label class="theme-changer__label" for="theme-changer-input-tablet" data-tooltip="Change theme"><input class="theme-changer__input" type="checkbox" id="theme-changer-input-tablet" data-theme-changer="data-theme-changer"/><span class="theme-changer__icon theme-changer__icon_light-theme"></span><span class="theme-changer__icon theme-changer__icon_dark-theme"></span><span class="theme-changer__label-text theme-changer__label-text_light-theme">Light theme</span><span class="theme-changer__label-text theme-changer__label-text_dark-theme">Dark theme</span></label></div></div></div><div class="tablet-menu__line"><div class="tablet-menu__content"><div class="share-icons"><span class="share-icons__title">シェア</span><a class="share share_tw" href="https://twitter.com/share?url=https%3A%2F%2Fja.javascript.info%2Fproxy" rel="nofollow"></a><a class="share share_fb" href="https://www.facebook.com/sharer/sharer.php?s=100&p%5Burl%5D=https%3A%2F%2Fja.javascript.info%2Fproxy" rel="nofollow"></a></div></div></div><div class="tablet-menu__line"><div class="tablet-menu__content"><select class="tablet-menu__nav input-select input-select input-select_small" onchange="if(this.value) window.location.href=this.value"><option value="https://ar.javascript.info/proxy">عربي</option><option value="https://javascript.info/proxy">English</option><option value="https://es.javascript.info/proxy">Español</option><option value="https://fa.javascript.info/">فارسی</option><option value="https://fr.javascript.info/proxy">Français</option><option value="https://id.javascript.info/">Indonesia</option><option value="https://it.javascript.info/proxy">Italiano</option><option value="https://ja.javascript.info/proxy" selected>日本語</option><option value="https://ko.javascript.info/proxy">한국어</option><option value="https://learn.javascript.ru/proxy">Русский</option><option value="https://tr.javascript.info/proxy">Türkçe</option><option value="https://uk.javascript.info/proxy">Українська</option><option value="https://zh.javascript.info/proxy">简体中文</option></select></div></div></div><progress class="tutorial-progress" data-sticky value="88" max="92" data-tooltip="レッスン 88 of 92"></progress></div><div class="page page_sidebar_on page_inner_padding"><script>if(localStorage.noSidebar){document.querySelector(".page").classList.remove("page_sidebar_on");let e=document.querySelector(".page-wrapper");e&&e.classList.remove("page-wrapper_sidebar_on")}setTimeout(function(){document.querySelector(".page").classList.add("page_sidebar-animation-on")});</script><div class="page__inner"><main class="main main_width-limit"><header class="main__header"><div class="main__header-inner"><div class="main__header-group"><ol class="breadcrumbs"><li class="breadcrumbs__item breadcrumbs__item_home"><a class="breadcrumbs__link" href="/"><span class="breadcrumbs__hidden-text">チュートリアル</span></a></li><li class="breadcrumbs__item" id="breadcrumb-1"><a class="breadcrumbs__link" href="/js"><span>JavaScript 言語</span></a></li><li class="breadcrumbs__item" id="breadcrumb-2"><a class="breadcrumbs__link" href="/js-misc"><span>その他</span></a></li><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"チュートリアル","item":"https://ja.javascript.info/"},{"@type":"ListItem","position":2,"name":"JavaScript 言語","item":"https://ja.javascript.info/js"},{"@type":"ListItem","position":3,"name":"その他","item":"https://ja.javascript.info/js-misc"}]}</script></ol><div class="updated-at" data-tooltip="最終更新日2022年12月4日"><div class="updated-at__content">2022年12月4日</div></div></div><h1 class="main__header-title">Proxy と Reflect</h1></div></header><div class="content"><article class="formatted" itemscope itemtype="http://schema.org/TechArticle"><meta itemprop="name" content="Proxy と Reflect"><div itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="email" content="iliakan@gmail.com"><meta itemprop="name" content="Ilya Kantor"></div><div itemprop="articleBody"><p><code>Proxy</code> オブジェクトは別のオブジェクトをラップし、プロパティやその他の読み取り/書き込みなどの操作をインターセプトします。必要に応じてそれらを独自に処理したり、オブジェクトが透過的にそれらを処理できるようにします。</p> <p>Proxy は多くのライブラリや一部のブラウザフレームワークで使われています。この章では、多くの実践的なアプリケーションを紹介します。</p> <h2><a class="main__anchor" name="ref-686" href="#ref-686">Proxy</a></h2><p>構文:</p> <div id="pr5c1xnkkc" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let proxy = new Proxy(target, handler)</code></pre> </div> </div> </div><ul> <li><code>target</code> – ラップするオブジェクトです。関数含め何でもOKです。</li> <li><code>handler</code> – プロキシ設定: 操作をインターセプトするメソッドである “トラップ” をもつオブジェクトです。例: <code>get</code> トラップは <code>target</code> のプロパティの読み取り用、<code>set</code> トラップは、<code>target</code> へのプロパティ書き込み用、など。</li> </ul> <p><code>proxy</code> の操作では、<code>handler</code> に対応するトラップがある場合はそれが実行されます。それ以外の場合は、操作は <code>target</code> で実行されます。</p> <p>最初の例として、トラップなしでプロキシを作ってみましょう。:</p> <div id="ujvive4vaj" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let target = {}; let proxy = new Proxy(target, {}); // 空のハンドラ proxy.test = 5; // プロキシへの書き込み (1) alert(target.test); // 5, プロパティが target で現れました! alert(proxy.test); // 5, proxy からの読み取ることができます (2) for(let key in proxy) alert(key); // test, イテレーションも機能します (3)</code></pre> </div> </div> </div><p>トラップがないので、<code>proxy</code> 上のすべての操作は <code>target</code> に転送されます。</p> <ol> <li>書き込み操作 <code>proxy.test=</code> は <code>target</code> に値を設定します。</li> <li>読み込み操作 <code>proxy.test</code> は <code>target</code> からの値を返します。</li> <li><code>proxy</code> のイテレートは、<code>target</code> からの値を返します。</li> </ol> <p>ご覧の通り、トラップがない場合は <code>proxy</code> は <code>target</code> に対する透過的なラッパーです。</p> <figure><div class="image" style="width:292px"> <div class="image__ratio" style="padding-top:61.64383561643836%"></div> <object type="image/svg+xml" data="/article/proxy/proxy.svg" width="292" height="180" class="image__image" data-use-theme> <img src="/article/proxy/proxy.svg" alt="" width="292" height="180"> </object> </div></figure><p><code>Proxy</code> は特別な “エキゾチックオブジェクト(exotic object)” です。<code>Proxy</code> は独自のプロパティは持っていません。空の <code>handler</code> の場合は、透過的に <code>target</code> へ操作を転送します。</p> <p>さらに機能を有効にするために、トラップを追加しましょう。</p> <p>これによって、何がインターセプトできるでしょう?</p> <p>オブジェクトに対するほとんどの操作に対しては、JavaScript の仕様で いわゆる “内部メソッド” と呼ばれるものがあり、仕様ではそれらがどのように動作するかを最も低レベルで説明しています。例えば、 <code>[[Get]]</code> は、プロパティを読み取るための内部メソッドで、<code>[[Set]]</code> はプロパティを書き込むための内部メソッド、などです。これらのメソッドは仕様でのみ使用されており、名前を使ってそれらを直接使用することはできません。</p> <p>プロキシのトラップはこれらのメソッドの呼び出しをインターセプトします。これらのメソッドは<a href="https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots">Proxy specification</a> 及び以下の表にリストされています。</p> <p>このテーブルに、すべての内部ソッドに対するトラップがあります: 操作をインターセプトするために <code>new Proxy</code> の <code>handler</code> パラメータに追加できるメソッド名です:</p> <table> <thead> <tr> <th>内部メソッド</th> <th>ハンドラメソッド</th> <th>いつ発生するか</th> </tr> </thead> <tbody> <tr> <td><code>[[Get]]</code></td> <td><code>get</code></td> <td>プロパティ読み取り時</td> </tr> <tr> <td><code>[[Set]]</code></td> <td><code>set</code></td> <td>プロパティ書き込み時</td> </tr> <tr> <td><code>[[HasProperty]]</code></td> <td><code>has</code></td> <td><code>in</code> 演算子</td> </tr> <tr> <td><code>[[Delete]]</code></td> <td><code>deleteProperty</code></td> <td><code>delete</code> 演算子</td> </tr> <tr> <td><code>[[Call]]</code></td> <td><code>apply</code></td> <td>関数呼び出し</td> </tr> <tr> <td><code>[[Construct]]</code></td> <td><code>construct</code></td> <td><code>new</code> 演算子</td> </tr> <tr> <td><code>[[GetPrototypeOf]]</code></td> <td><code>getPrototypeOf</code></td> <td><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf">Object.getPrototypeOf</a></td> </tr> <tr> <td><code>[[SetPrototypeOf]]</code></td> <td><code>setPrototypeOf</code></td> <td><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf">Object.setPrototypeOf</a></td> </tr> <tr> <td><code>[[IsExtensible]]</code></td> <td><code>isExtensible</code></td> <td><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible">Object.isExtensible</a></td> </tr> <tr> <td><code>[[PreventExtensions]]</code></td> <td><code>preventExtensions</code></td> <td><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions">Object.preventExtensions</a></td> </tr> <tr> <td><code>[[DefineOwnProperty]]</code></td> <td><code>defineProperty</code></td> <td><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty">Object.defineProperty</a>, <a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties">Object.defineProperties</a></td> </tr> <tr> <td><code>[[GetOwnProperty]]</code></td> <td><code>getOwnPropertyDescriptor</code></td> <td><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor">Object.getOwnPropertyDescriptor</a>, <code>for..in</code>, <code>Object.keys/values/entries</code></td> </tr> <tr> <td><code>[[OwnPropertyKeys]]</code></td> <td><code>ownKeys</code></td> <td><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames">Object.getOwnPropertyNames</a>, <a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols">Object.getOwnPropertySymbols</a>, <code>for..in</code>, <code>Object.keys/values/entries</code></td> </tr> </tbody> </table> <div class="important important_warn"> <div class="important__header"><span class="important__type">Invariants</span></div> <div class="important__content"><p>JavaScript にはいくつかの不変条件(内部メソッドと トラップによって満たされるべき条件)があります。</p> <p>そのほとんどは戻り値に関してです:</p> <ul> <li><code>[[Set]]</code> は値が正常に書き込まれた場合には <code>true</code> を、そうでなければ <code>false</code> を返す必要があります。</li> <li><code>[[Delete]]</code> は値が正常に削除された場合には <code>true</code> を、そうでなければ <code>false</code> を返す必要があります。</li> <li>…などです。以下の例で詳しく見ていきます。</li> </ul> <p>他にも以下のようないくつかの不変条件があります:</p> <ul> <li>proxy オブジェクトに適用される <code>[[GetPrototypeOf]]</code> は proxy オブジェクトのターゲットオブジェクトに適用される <code>[[GetPrototypeOf]]</code> と同じ値を返さなければなりません。つまり、proxy のプロトタイプを参照すると、常にターゲットオブジェクトのプロトタイプが返却される必要があります。</li> </ul> <p>traps はこれらの操作をインターセプトできますが、これらのルールには従う必要があります。</p> <p>不変条件は、言語機能の正しさと一貫した動作を保証するものです。完全な不変条件のリストは <a href="https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots">仕様</a>にありますが、変なことをしない限りは違反することはないでしょう。</p> </div></div> <p>実際の例でそれがどのように動作するのかを見てみましょう。</p> <h2><a class="main__anchor" name="ref-687" href="#ref-687">“get” トラップでのデフォルト値</a></h2><p>最も一般的なトラップ(traps)はプロパティの読み書きです。</p> <p>読み取りをインターセプトするには、<code>handler</code> に <code>get(target, property, receiver)</code> が必要です。</p> <p>これはプロパティが読み取られたとき、以下の引数で実行されます。:</p> <ul> <li><code>target</code>: <code>new Proxy</code> の最初の引数として渡されるターゲットオブジェクトです。</li> <li><code>property</code> – プロパティ名,</li> <li><code>receiver</code> --ターゲットプロパティが getter の場合、<code>receiver</code> はその呼び出しの中で <code>this</code> として使われるオブジェクトです。通常、これは <code>proxy</code> オブジェクト自身(あるいは、proxy から継承している場合は、継承したオブジェクト)です。現時点ではこの引数は不要です。詳細については後ほど説明します。</li> </ul> <p>オブジェクトのデフォルト値を実装するのに <code>get</code> を使ってみましょう。</p> <p>存在しない値の場合 <code>0</code> を返す数値配列を作ります。</p> <p>通常、存在しない値を取得しようとすると <code>undefined</code> になりますが、ここでは通常の配列に対して、プロパティが存在しない場合に <code>0</code> を返すプロキシでラップします。:</p> <div id="gdae77qbb0" data-trusted="1" class="code-example" data-highlight="[{"start":12,"end":13}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let numbers = [0, 1, 2]; numbers = new Proxy(numbers, { get(target, prop) { if (prop in target) { return target[prop]; } else { return 0; // デフォルト値 } } }); alert( numbers[1] ); // 1 alert( numbers[123] ); // 0 (このような項目はなし)</code></pre> </div> </div> </div><p>ご覧の通り、<code>get</code> トラップを使用するのは非常に簡単です。</p> <p><code>Proxy</code> を利用すると、任意の “デフォルト値” 用のロジックを組むことができます。</p> <p>想像してください、フレーズと一緒に翻訳を持つ辞書があるとします:</p> <div id="4nwqqwtlaf" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let dictionary = { 'Hello': 'Hola', 'Bye': 'Adiós' }; alert( dictionary['Hello'] ); // Hola alert( dictionary['Welcome'] ); // undefined</code></pre> </div> </div> </div><p>現在、フレーズがない場合、<code>dictionary</code> の読み取りは <code>undefined</code> を返します。しかし、実際には <code>undefined</code> よりも未翻訳のままのフレーズを残すほうがよいです。なので、このような場合に <code>undefined</code> ではなく、未翻訳のフレーズを返すようにしましょう。</p> <p>そのためには、<code>directory</code> を読み取り操作をインターセプトするプロキシでラップします。:</p> <div id="sm412ffz2o" data-trusted="1" class="code-example" data-highlight="[{"start":19,"end":19},{"start":6,"end":6}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let dictionary = { 'Hello': 'Hola', 'Bye': 'Adiós' }; dictionary = new Proxy(dictionary, { get(target, phrase) { // 辞書(dictionary)からのプロパティ読み取りをインターセプト if (phrase in target) { // 辞書の中にある場合 return target[phrase]; // 翻訳を返します } else { // そうでなければフレーズをそのまま返します return phrase; } } }); // 辞書で任意のフレーズを検索します // 辞書にない場合は翻訳されません alert( dictionary['Hello'] ); // Hola alert( dictionary['Welcome to Proxy']); // Welcome to Proxy</code></pre> </div> </div> </div><div class="important important_smart"> <div class="important__header"><span class="important__type">注意:</span></div> <div class="important__content"><p>プロキシがどのように変数を上書きするかに注意してください。:</p> <div id="hm1bi9yr4f" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>dictionary = new Proxy(dictionary, ...);</code></pre> </div> </div> </div><p>プロキシはどこでもターゲットオブジェクトを完全に置き換える必要があります。プロキシされた後はターゲットオブジェクトを参照しないでください。参照すると、簡単に台無しになります。</p> </div></div> <h2><a class="main__anchor" name="ref-688" href="#ref-688">“set” トラップでのバリデーション</a></h2><p>数値専用の配列がほしいとしましょう。別の型の値が追加された場合、エラーにする必要があります。</p> <p><code>set</code> トラップはプロパティが書き込まれたときに発生します。</p> <p><code>set(target, property, value, receiver)</code>:</p> <ul> <li><code>target</code>: <code>new Proxy</code> の最初の引数として渡されるターゲットオブジェクトです。</li> <li><code>property</code>: プロパティ名</li> <li><code>value</code>: プロパティ値,</li> <li><code>receiver</code>: <code>get</code> と同様で、setter プロパティに関係します。</li> </ul> <p><code>set</code> トラップは設定が成功すると <code>true</code> を、それ以外の場合は <code>false</code> (<code>TypeError</code> が発生)を返す必要があります。</p> <p>新しい値を検証するのに使って見ましょう:</p> <div id="sdy9bcrsls" data-trusted="1" class="code-example" data-highlight="[{"start":17,"end":17},{"start":3,"end":3}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let numbers = []; numbers = new Proxy(numbers, { // (*) set(target, prop, val) { // プロパティの書き込みをインターセプト if (typeof val == 'number') { target[prop] = val; return true; } else { return false; } } }); numbers.push(1); // 追加成功 numbers.push(2); // 追加成功 alert("Length is: " + numbers.length); // 2 numbers.push("test"); // TypeError (プロキシの 'set' が false を返却) alert("This line is never reached (error in the line above)");</code></pre> </div> </div> </div><p>注目してください: 配列の組み込みの機能は依然として動作します! 値は <code>push</code> により追加されました。<code>length</code> プロパティは値が追加されたときにオートインクリメントされます。プロキシは何も破壊していません。</p> <p>我々はチェック処理を追加するのに <code>push</code> や <code>unshift</code> のような、値を追加する配列メソッドを上書きする必要はありません。なぜなら、それらは内部的には <code>[[Set]]</code> 操作を使用しており、プロキシによりインターセプトされるからです。</p> <p>したがって、コードはクリーンであり簡潔です。</p> <div class="important important_warn"> <div class="important__header"><span class="important__type"><code>true</code> を返すのを忘れないでください</span></div> <div class="important__content"><p>上記のように、維持すべき条件があります。</p> <p><code>set</code> の場合、書き込みの成功に対しては <code>true</code> を返さなければなりません。</p> <p>それを忘れたり false を返すと、操作は <code>TypeError</code> をトリガーします。</p> </div></div> <h2><a class="main__anchor" name="ref-689" href="#ref-689">“ownKeys” と “getOwnPropertyDescriptor” によるイテレーション</a></h2><p><code>Object.keys</code>, <code>for..in</code> ループ及びオブジェクトプロパティをイテレートする他のほとんどのメソッドは <code>[[OwnPropertyKeys]]</code> 内部メソッド(<code>ownKeys</code> トラップによりインターセプトされる)を使用してプロパティのリストを取得しています。</p> <p>このようなメソッドの詳細は異なります:</p> <ul> <li><code>Object.getOwnPropertyNames(obj)</code> は “非” シンボルキーを返します。</li> <li><code>Object.getOwnPropertySymbols(obj)</code> はシンボルキーを返します。</li> <li><code>Object.keys/values()</code> は <code>enumerable</code> フラグ(プロパティフラグについては、チャプター <a href="/property-descriptors">プロパティフラグとディスクリプタ</a> に説明があります)を持つ非シンボルのキー/バリュー値を返します。</li> <li><code>for..in</code> は <code>enumerable</code> フラグを持つ非シンボルキーとプロトタイプキーをループします。</li> </ul> <p>…しかし、これらはすべてその内部メソッドで得られたリストから始まります。</p> <p>以下の例では、<code>ownKeys</code> トラップを使用して <code>user</code> に対する <code>for..in</code> ループを行い、また <code>Object.keys</code> や <code>Object.values</code> を行っています。これらはアンダースコア <code>_</code> で始まるプロパティをスキップします。:</p> <div id="rrw221puo6" data-trusted="1" class="code-example" data-highlight="[{"start":7,"end":7}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { name: "John", age: 30, _password: "***" }; user = new Proxy(user, { ownKeys(target) { return Object.keys(target).filter(key => !key.startsWith('_')); } }); // "ownKeys" は _password を除外します for(let key in user) alert(key); // name, then: age // これらのメソッドへも同じ影響があります: alert( Object.keys(user) ); // name,age alert( Object.values(user) ); // John,30</code></pre> </div> </div> </div><p>これまでのところ、期待通り動作しています。</p> <p>ですが、もしオブジェクトに存在しないキーを返した場合、<code>Object.keys</code> はそれをリストしません:</p> <div id="x9ey87t8um" data-trusted="1" class="code-example" data-highlight="[{"start":3,"end":3}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { }; user = new Proxy(user, { ownKeys(target) { return ['a', 'b', 'c']; } }); alert( Object.keys(user) ); // <empty></code></pre> </div> </div> </div><p>なぜでしょう?理由は簡単です。: <code>Object.keys</code> は <code>enumerable</code> フラグを持つプロパティだけを返すからです。それを確かめるため、すべてのメソッドに対し内部メソッド <code>[[GetOwnProperty]]</code> を呼び出し,<a href="/property-descriptors">ディスクリプタ</a> を取得します。すると、ここではプロパティがないので、そのディスクリプタは空であり、<code>enumerable</code> フラグがありません。そのため、スキップされます。</p> <p><code>Object.keys</code> がプロパティを返すには、<code>enumerable</code> 付きでオブジェクトに存在するか、<code>[[GetOwnProperty]]</code>(トラップは <code>getOwnPropertyDescriptor</code>)の呼び出しをインターセプトし、<code>enumerable: true</code> を持つディスクリプタを返します。</p> <p>これはそのコードです:</p> <div id="lgssi3ws35" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { }; user = new Proxy(user, { ownKeys(target) { // プロパティのリストを取得するために一度だけ呼ばれます return ['a', 'b', 'c']; }, getOwnPropertyDescriptor(target, prop) { // プロパティ毎に呼ばれます return { enumerable: true, configurable: true /* ...other flags, probable "value:..."" */ }; } }); alert( Object.keys(user) ); // a, b, c</code></pre> </div> </div> </div><p>改めて留意してください: <code>[[GetOwnProperty]]</code> をインターセプトする必要があるのは、プロパティがオブジェクトにない場合のみです。</p> <h2><a class="main__anchor" name="ref-690" href="#ref-690">“deleteProperty” 及び他のトラップで保護されたプロパティ</a></h2><p>アンダースコア <code>_</code> で始まるプロパティやメソッドは内部的なものであるということは、広く知られた慣習です。それらはオブジェクトの外からアクセスされるべきではありません。</p> <p>ですが、技術的には可能です:</p> <div id="y5gsunzzdh" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { name: "John", _password: "secret" }; alert(user._password); // secret</code></pre> </div> </div> </div><p>プロキシを使用して、<code>_</code> で始まるプロパティへのアクセスを防ぎましょう。</p> <p>次のトラップが必要です:</p> <ul> <li><code>get</code>: そのようなプロパティの読み込み時にエラーをスロー,</li> <li><code>set</code>: 書き込み時にエラーをスロー,</li> <li><code>deleteProperty</code>: 削除時にエラーをスロー,</li> <li><code>ownKeys</code>: <code>for..in</code> や <code>Object.keys</code> のようなメソッドから <code>_</code> で始まるプロパティを除外</li> </ul> <p>これがそのコードです:</p> <div id="5s0hifglgt" data-trusted="1" class="code-example" data-highlight="[{"start":29,"end":29},{"start":21,"end":21},{"start":13,"end":13},{"start":6,"end":6}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { name: "John", _password: "***" }; user = new Proxy(user, { get(target, prop) { if (prop.startsWith('_')) { throw new Error("Access denied"); } let value = target[prop]; return (typeof value === 'function') ? value.bind(target) : value; // (*) }, set(target, prop, val) { // プロパティの書き込みをインターセプト if (prop.startsWith('_')) { throw new Error("Access denied"); } else { target[prop] = val; return true; } }, deleteProperty(target, prop) { // プロパティの削除をインターセプト if (prop.startsWith('_')) { throw new Error("Access denied"); } else { delete target[prop]; return true; } }, ownKeys(target) { // プロパティのリストをインターセプト return Object.keys(target).filter(key => !key.startsWith('_')); } }); // "get" は _password の読み込みを許可しません try { alert(user._password); // Error: Access denied } catch(e) { alert(e.message); } // "set" は _password の書き込みを許可しません try { user._password = "test"; // Error: Access denied } catch(e) { alert(e.message); } // "deleteProperty" は _password の削除を許可しません try { delete user._password; // Error: Access denied } catch(e) { alert(e.message); } // "ownKeys" は _password を除外します for(let key in user) alert(key); // name</code></pre> </div> </div> </div><p><code>(*)</code> 行の <code>get</code> トラップの重要な点に注意してください:</p> <div id="jznwtjodmb" data-trusted="1" class="code-example" data-highlight="[{"start":3,"end":3}]"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>get(target, prop) { // ... let value = target[prop]; return (typeof value === 'function') ? value.bind(target) : value; // (*) }</code></pre> </div> </div> </div><p>なぜ関数の場合に <code>value.bind(target)</code> を呼び出す必要があるのでしょうか?</p> <p>理由は <code>user.checkPassword()</code> のようなオブジェクトメソッドは <code>_password</code> へアクセスできる必要があるからです。:</p> <div id="elruadzzym" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>user = { // ... checkPassword(value) { // オブジェクトメソッドは _password へアクセスできなければいけません return value === this._password; } }</code></pre> </div> </div> </div><p><code>user.checkPassword()</code> の呼び出しはプロキシされた <code>user</code> を <code>this</code> (ドットの前のオブジェクトが <code>this</code> になります)として取得するため、<code>this._password</code> へのアクセスを試みると <code>get</code> トラップが機能(これはあらゆるプロパティ読み取りでトリガーされます)し、エラーをスローします。</p> <p>そのため、<code>(*)</code> の通りオブジェクトメソッドのコンテキストを元のオブジェクトである <code>target</code> でバインドします。以降、その呼び出しでは <code>this</code> としてトラップのない <code>target</code> を使用します。</p> <p>この解決策はたいてい動作しますが、メソッドがプロキシされていないオブジェクトを別の場所に渡す可能性があるため理想的ではありません。これは混乱のもとになります: どこにオリジナルのオブジェクトがあり、どれがプロキシされたものなのか。</p> <p>さらに、オブジェクトが何度もプロキシされる可能性もあります(複数のプロキシがそれぞれ異なる “微調整” をオブジェクトにする場合があります)。また、メソッドにラップされていないオブジェクトを渡した場合、予期しない結果になる可能性もあります。</p> <p>したがって、このようなプロキシは使用しないことを推奨します。</p> <div class="important important_smart"> <div class="important__header"><span class="important__type">クラスの private プロパティ</span></div> <div class="important__content"><p>モダンな JavaScript エンジンはクラスの private プロパティをネイティブにサポートします(<code>#</code> から始まります)。これについてはチャプター <a href="/private-protected-properties-methods">Private / protected プロパティとメソッド</a> で記載しています。プロキシは必要ありません。</p> <p>ただし、このようなプロパティにも問題はあります。特にこれらは継承されません。</p> </div></div> <h2><a class="main__anchor" name="ref-691" href="#ref-691">“has” トラップを使用した “範囲内”</a></h2><p>他の例を見てみましょう。</p> <p>範囲を持つオブジェクトがあります:</p> <div id="sefpckffza" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let range = { start: 1, end: 10 };</code></pre> </div> </div> </div><p><code>in</code> 演算子を使って、 数値が <code>range</code> の範囲内にあるかを確認します。</p> <p><code>has</code> トラップは <code>in</code> 呼び出しをインターセプトします。</p> <p><code>has(target, property)</code></p> <ul> <li><code>target</code> – <code>new Proxy</code> への最初の引数として渡されるターゲットオブジェクト</li> <li><code>property</code> – プロパティ名</li> </ul> <p>デモです:</p> <div id="ybz6qes7q9" data-trusted="1" class="code-example" data-highlight="[{"start":11,"end":12},{"start":6,"end":6}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let range = { start: 1, end: 10 }; range = new Proxy(range, { has(target, prop) { return prop >= target.start && prop <= target.end } }); alert(5 in range); // true alert(50 in range); // false</code></pre> </div> </div> </div><p>良い糖衣構文ですね。それに実装もとても簡単です。</p> <h2><a class="main__anchor" name="ref-692" href="#ref-692">Wrapping functions: “apply”</a></h2><p>関数の周りに対しても同様に proxy をラップすることができます。</p> <p><code>apply(target, thisArg, args)</code> トラップはプロキシを関数として呼び出すよう処理をします:</p> <ul> <li><code>target</code> はターゲットオブジェクトです(JavaScript では関数はオブジェクトです),</li> <li><code>thisArg</code> は <code>this</code> の値です</li> <li><code>args</code> は引数のリストです</li> </ul> <p>例えば、チャプター <a href="/call-apply-decorators">デコレータと転送, call/apply</a> で行った <code>delay(f, ms)</code> デコレータを思い出してください。</p> <p>そのチャプターでは、proxy を使わずに実現しました。<code>delay(f, ms)</code> の呼び出しは、<code>ms</code> ミリ秒後に <code>f</code> の呼び出しを行う関数を返しました。</p> <p>これは以前の関数ベースの実装です:</p> <div id="9g5rpyi8vy" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>function delay(f, ms) { // タイムアウト後に f への呼び出しを渡すラッパー関数を返します return function() { // (*) setTimeout(() => f.apply(this, arguments), ms); }; } function sayHi(user) { alert(`Hello, ${user}!`); } // このラップをすると、sahHi 呼び出しは 3秒間遅延します sayHi = delay(sayHi, 3000); sayHi("John"); // Hello, John! (3秒後)</code></pre> </div> </div> </div><p>すでにご覧になったように、これはほぼほぼ機能します。ラッパー関数 <code>(*)</code> はタイムアウト後に呼び出しを実行します。</p> <p>しかし、ラッパー関数はプロパティの読み書き操作などは転送しません。ラップした後、<code>name</code> や <code>length</code> などの元の関数のプロパティへのアクセスは失われます。:</p> <div id="fiji7f7hgc" data-trusted="1" class="code-example" data-highlight="[{"start":14,"end":14},{"start":10,"end":10}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>function delay(f, ms) { return function() { setTimeout(() => f.apply(this, arguments), ms); }; } function sayHi(user) { alert(`Hello, ${user}!`); } alert(sayHi.length); // 1 (function.length は宣言された関数の引数の数を返します) sayHi = delay(sayHi, 3000); alert(sayHi.length); // 0 (ラッパー後は引数は 0 です)</code></pre> </div> </div> </div><p><code>Proxy</code> はすべてをターゲットオブジェクトに転送するので、はるかに強力です。</p> <p>関数ラッピングの代わりに <code>Proxy</code> を使って見ましょう:</p> <div id="uco16g2vp6" data-trusted="1" class="code-example" data-highlight="[{"start":14,"end":14}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>function delay(f, ms) { return new Proxy(f, { apply(target, thisArg, args) { setTimeout(() => target.apply(thisArg, args), ms); } }); } function sayHi(user) { alert(`Hello, ${user}!`); } sayHi = delay(sayHi, 3000); alert(sayHi.length); // 1 (*) プロキシは length 操作をターゲットに転送します sayHi("John"); // Hello, John! (3秒後)</code></pre> </div> </div> </div><p>結果は同じですが、呼び出しだけでなく、プロキシ上のすべての操作は元の関数に転送されます。そのため、行 <code>(*)</code> で <code>sayHi.length</code> はラッピング後も正しい値を返します。</p> <p>これで “よりリッチな” ラッパーを手に入れました。</p> <p>他にもトラップはあります: 完全なリストはこのチャプターの最初にのせています。それらの使用パターンは上記と同じです。</p> <h2><a class="main__anchor" name="ref-693" href="#ref-693">Reflect</a></h2><p><code>Reflect</code> は <code>Proxy</code> の作成を簡単にする組み込みのオブジェクトです。</p> <p>以前説明したとおり、<code>[[Get]]</code>, <code>[[Set]]</code> やその他の内部メソッドは仕様上のものであり、直接呼び出すことはできません。</p> <p><code>Reflect</code> オブジェクトはそれをいくらか可能にします。それのもつメソッドは内部メソッドの最小限のラッパーです。</p> <p>ここでは、操作と、それと同じことをする <code>Reflect</code> 呼び出しの例を示します:</p> <table> <thead> <tr> <th>操作</th> <th><code>Reflect</code> 呼び出し</th> <th>内部メソッド</th> </tr> </thead> <tbody> <tr> <td><code>obj[prop]</code></td> <td><code>Reflect.get(obj, prop)</code></td> <td><code>[[Get]]</code></td> </tr> <tr> <td><code>obj[prop] = value</code></td> <td><code>Reflect.set(obj, prop, value)</code></td> <td><code>[[Set]]</code></td> </tr> <tr> <td><code>delete obj[prop]</code></td> <td><code>Reflect.deleteProperty(obj, prop)</code></td> <td><code>[[HasProperty]]</code></td> </tr> <tr> <td><code>new F(value)</code></td> <td><code>Reflect.construct(F, value)</code></td> <td><code>[[Construct]]</code></td> </tr> <tr> <td>…</td> <td>…</td> <td>…</td> </tr> </tbody> </table> <p>例:</p> <div id="zch6guxblc" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = {}; Reflect.set(user, 'name', 'John'); alert(user.name); // John</code></pre> </div> </div> </div><p>特に、<code>Reflect</code> では演算子 (<code>new</code>, <code>delete</code>…) を関数(<code>Reflect.construct</code>, <code>Reflect.deleteProperty</code>, …)として呼び出すことができます。これは興味深い機能ですが、ここでは別に重要な部分があります。</p> <p><strong><code>Proxy</code> でトラップ可能なすべての内部メソッドに対し、<code>Reflect</code> には <code>Proxy</code> トラップと同じ名前、引数を持つ対応するメソッドがあります。</strong></p> <p>したがって、<code>Reflect</code> を使って操作を元のオブジェクトに転送することができます。</p> <p>この例では、<code>get</code> と <code>set</code> の両方のトラップが、読み書き操作をオブジェクトへ透過的(存在しないかのように)に転送し、メッセージを表示します。:</p> <div id="ipw56idhkb" data-trusted="1" class="code-example" data-highlight="[{"start":11,"end":11},{"start":7,"end":7}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { name: "John", }; user = new Proxy(user, { get(target, prop, receiver) { alert(`GET ${prop}`); return Reflect.get(target, prop, receiver); // (1) }, set(target, prop, val, receiver) { alert(`SET ${prop}=${val}`); return Reflect.set(target, prop, val, receiver); // (2) } }); let name = user.name; // "GET name" を表示 user.name = "Pete"; // "SET name=Pete" を表示</code></pre> </div> </div> </div><p>Here:</p> <ul> <li><code>Reflect.get</code> はオブジェクトプロパティを読み取ります。</li> <li><code>Reflect.set</code> はオブジェクトプロパティの書き込みを行い、成功すれば <code>true</code> を返します。それ以外の場合は <code>false</code> を返します。</li> </ul> <p>つまり、すべては単純です: トラップが呼び出しをオブジェクトに転送したい場合、同じ引数で <code>Reflect.<method></code> を呼べばよいです。</p> <p>ほとんどの場合で、<code>Reflect</code> を使うことなく同じことができます。例えば、プロパティの読み取り <code>Reflect.get(target, prop, receiver)</code> は <code>target[prop]</code> に置き換えることができます。ですが、重要な意味合いがあります。</p> <h3><a class="main__anchor" name="ref-694" href="#ref-694">ゲッター(getter)のプロキシ</a></h3><p>なぜ <code>Reflect.get</code> が優れている理由を示すデモを見てみましょう。合わせて、なぜ <code>get/set</code> が4番目の引数 <code>receiver</code> を持っているのか(これは以前は使用していませんでした)も見ていきましょう。</p> <p><code>_name</code> プロパティをもつ <code>user</code> オブジェクトがあり、そのゲッターをします:</p> <p>これはそのプロキシです:</p> <div id="77vcx5t9f9" data-trusted="1" class="code-example" data-highlight="[{"start":7,"end":11}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { _name: "Guest", get name() { return this._name; } }; let userProxy = new Proxy(user, { get(target, prop, receiver) { return target[prop]; } }); alert(userProxy.name); // Guest</code></pre> </div> </div> </div><p>ここでは、<code>get</code> トラップは明白です。元のプロパティを返し、他には何もしていません。今回の例ではこれで十分です。</p> <p>今のところすべて問題ありません。では例をもう少し複雑にしてみましょう。</p> <p><code>user</code> から別のオブジェクト <code>admin</code> を継承すると、正しくない振る舞いが起きます:</p> <div id="v75dtnfrua" data-trusted="1" class="code-example" data-highlight="[{"start":13,"end":19}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { _name: "Guest", get name() { return this._name; } }; let userProxy = new Proxy(user, { get(target, prop, receiver) { return target[prop]; // (*) target = user } }); let admin = { __proto__: userProxy, _name: "Admin" }; // 期待値: Admin alert(admin.name); // 出力: Guest (?!?)</code></pre> </div> </div> </div><p><code>admin.name</code> の読み取りは <code>"Guest"</code> ではなく <code>"Admin"</code> を返すべきです!</p> <p>何が起きたのでしょうか?継承になにか問題があったのでしょうか?</p> <p>ですが、プロキシを削除するとすべて期待通りに動作します。</p> <p>問題は行 <code>(*)</code> のプロキシの中にあります。</p> <ol> <li> <p><code>admin.name</code> を読み取るとき、<code>admin</code> オブジェクトにはそのようなプロパティはないため、検索はそのプロトタイプに進みます。</p> </li> <li> <p>プロトタイプは <code>userProxy</code> です。</p> </li> <li> <p>プロキシから <code>name</code> プロパティを読み取ると、<code>get</code> トラップが発生し、行 <code>(*)</code> で <code>target[prop]</code> により元のオブジェクトから返却されます。</p> <p><code>prop</code> がゲッターである場合、<code>target[prop]</code> の呼び出しはコンテキスト <code>this=target</code> でコードが実行されます。そのため、結果は元のオブジェクト <code>target</code>, つまり <code>user</code> からの <code>this._name</code> になります。</p> </li> </ol> <p>これを修正するには、<code>get</code> トラップの3番目の引数である <code>receiver</code> が必要です。これによりゲッターに正しい <code>this</code> を渡すことができます。今回のケースだと、<code>admin</code> です。</p> <p>どうやってゲッターへコンテキストを渡すのでしょう?通常の関数では <code>call/apply</code> を使いますが、これはゲッターなので “呼び出される” のではなく、単なるアクセスです。</p> <p><code>Reflect.get</code> はそれをすることができます。これを使うことですべてが上手く動きます。</p> <p>修正されたバリアントです:</p> <div id="xq40pahvca" data-trusted="1" class="code-example" data-highlight="[{"start":19,"end":19},{"start":9,"end":9}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { _name: "Guest", get name() { return this._name; } }; let userProxy = new Proxy(user, { get(target, prop, receiver) { // receiver = admin return Reflect.get(target, prop, receiver); // (*) } }); let admin = { __proto__: userProxy, _name: "Admin" }; alert(admin.name); // Admin</code></pre> </div> </div> </div><p>上のコードでは、正しい <code>this</code> (つまり <code>admin</code>) への参照を維持する <code>receiver</code> は、行 <code>(*)</code> で <code>Reflect.get</code> を使用したゲッターに渡されます。</p> <p>トラップをさらに短く書くこともできます:</p> <div id="al500l3skh" data-trusted="1" class="code-example" data-highlight="[{"start":1,"cols":[{"start":21,"end":33}]}]"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>get(target, prop, receiver) { return Reflect.get(...arguments); }</code></pre> </div> </div> </div><p><code>Reflect</code> 呼び出しはトラップとまったく同じ名前が付けられており、同じ引数を受け付けます。特別にそのように設計されました。</p> <p>したがって、<code>return Reflect...</code> は安全かつ考えるまでもない分かりやすい手段で操作を転送することができます。</p> <h2><a class="main__anchor" name="ref-695" href="#ref-695">プロキシの制限</a></h2><p>プロキシは既存のオブジェクトの動作を最も低いレベルで変更したり微調整する独自の方法を提供します。それでも完璧ではありません。いくつか制限があります。</p> <h3><a class="main__anchor" name="ref-696" href="#ref-696">組み込みオブジェクト: 内部スロット(Internal slots)</a></h3><p><code>Map</code>, <code>Set</code>, <code>Date</code>, <code>Promise</code> などの多くの組み込みオブジェクトは、いわゆる “内部スロット” を使用します。</p> <p>それらはプロパティに似ていますが、内部で仕様専用の目的で予約されています。例えば、<code>Map</code> は内部スロット <code>[[MapData]]</code> にアイテムを保存します。組み込みのメソッドは、<code>[[Get]]/[[Set]]</code> 内部メソッド経由ではなく、直接アクセスします。そのため、<code>Proxy</code> はインターセプトすることができません。</p> <p>内部の話なのに気にする必要はあるのでしょうか?</p> <p>ここに問題があります。このような組み込みのオブジェクトがプロキシされると、プロキシはこれらの内部スロットを持たないため、組み込みのメソッドは失敗します。</p> <p>例:</p> <div id="bc6aty1r0o" data-trusted="1" class="code-example" data-highlight="[{"start":4,"end":4}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let map = new Map(); let proxy = new Proxy(map, {}); proxy.set('test', 1); // Error</code></pre> </div> </div> </div><p>内部的に、<code>Map</code> はすべてのデータを <code>[[MapData]]</code> 内部スロットに保存します。プロキシはそのようなスロットはありません。<a href="https://tc39.es/ecma262/#sec-map.prototype.set">組み込みのメソッド <code>Map.prototype.set</code></a> メソッドは内部プロパティ <code>this.[[MapData]]</code> にアクセスしようとしますが、<code>this=proxy</code> なので <code>proxy</code> 内には見つけることができず失敗します。</p> <p>幸いなことに、修正する方法があります:</p> <div id="tlega9fvdw" data-trusted="1" class="code-example" data-highlight="[{"start":5,"end":5}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let map = new Map(); let proxy = new Proxy(map, { get(target, prop, receiver) { let value = Reflect.get(...arguments); return typeof value == 'function' ? value.bind(target) : value; } }); proxy.set('test', 1); alert(proxy.get('test')); // 1 (works!)</code></pre> </div> </div> </div><p>上の例では、<code>get</code> トラップは <code>map.set</code> などの関数プロパティをターゲットオブジェクト(<code>map</code>)自身にバインドするので、問題なく動作します。</p> <p>これまでの例とは違い、<code>proxy.set(...)</code> 内での <code>this</code> の値は <code>proxy</code> ではなく元の <code>map</code> になります。そのため、<code>set</code> の内部実装が <code>this.[[MapData]]</code> 内部スロットにアクセスするのは成功します。</p> <div class="important important_smart"> <div class="important__header"><span class="important__type"><code>Array</code> には内部スロットがありません</span></div> <div class="important__content"><p>注目すべき例外です: 組み込みの <code>Array</code> は内部スロットを使用していません。<code>Array</code> はずっと以前から存在していたこともあり、歴史的な理由によるものです。</p> <p>したがって配列をプロキシする際にはこのような問題は起こりません。</p> </div></div> <h3><a class="main__anchor" name="ref-697" href="#ref-697">プライベートフィールド</a></h3><p>似たようなことがプライベートクラスフィールドでも起こります。</p> <p>例えば、<code>getName()</code> メソッドはプロキシ後にプライベート <code>#name</code> プロパティへアクセスすると壊れます。:</p> <div id="3jhh9rd33r" data-trusted="1" class="code-example" data-highlight="[{"start":12,"end":12}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>class User { #name = "Guest"; getName() { return this.#name; } } let user = new User(); user = new Proxy(user, {}); alert(user.getName()); // Error</code></pre> </div> </div> </div><p>これは、プライベートフィールドが内部スロットを使用して実装されているからです。JavaScript はそれらにアクセスする際、<code>[[Get]]/[[Set]]</code> は使用しません。</p> <p><code>getName()</code> の呼び出しでは、<code>this</code> の値はプロキシされた <code>user</code> であり、プライベートフィールドのスロットを持っていません。</p> <p>この場合も、メソッドをバインドする方法で機能させることができます:</p> <div id="opmpvlog88" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>class User { #name = "Guest"; getName() { return this.#name; } } let user = new User(); user = new Proxy(user, { get(target, prop, receiver) { let value = Reflect.get(...arguments); return typeof value == 'function' ? value.bind(target) : value; } }); alert(user.getName()); // Guest</code></pre> </div> </div> </div><p>ただし、この解決策にも欠点があります。以前説明したとおり、この方法は元のオブジェクトをメソッドに公開するので、メソッドの処理によってはさらにオブジェクトが渡される可能性があり、他のプロキシされた機能を破壊する可能性があります。</p> <h3><a class="main__anchor" name="ref-698" href="#ref-698">Proxy != target</a></h3><p>Proxy と元のオブジェクトは異なるオブジェクトです。これは当然ですね。</p> <p>なので、元のオブジェクトをキーとして使用し、その後プロキシすると、プロキシは見つかりません。:</p> <div id="j2i0bcj5b3" data-trusted="1" class="code-example" data-highlight="[{"start":15,"end":15}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let allUsers = new Set(); class User { constructor(name) { this.name = name; allUsers.add(this); } } let user = new User("John"); alert(allUsers.has(user)); // true user = new Proxy(user, {}); alert(allUsers.has(user)); // false</code></pre> </div> </div> </div><p>ご覧の通り、プロキシ後はセット <code>allUsers</code> で <code>user</code> を見つけることができません。プロキシは異なるオブジェクトだからです。</p> <div class="important important_warn"> <div class="important__header"><span class="important__type">プロキシは厳密等価 <code>===</code> をインターセプトすることはできません</span></div> <div class="important__content"><p>プロキシは <code>new</code>(<code>construct</code>), <code>in</code>(<code>has</code>), <code>delete</code>(<code>deleteProperty</code>)などの多くの演算子をインターセプトすることができます。</p> <p>しかし、オブジェクトへの厳密等価テストをインターセプトする方法はありません。オブジェクトは自身にのみ厳密に等しく、他の値とは等しくありません。</p> <p>したがって、オブジェクトの等価を比較するすべての演算子と組み込みのクラスはオブジェクトとプロキシを区別します。ここには透過的な替わりはありません。</p> </div></div> <h2><a class="main__anchor" name="ref-699" href="#ref-699">取り消し可能(revocable)なプロキシ</a></h2><p><em>取り消し可能(revocable)</em> なプロキシは、無効にすることのできるプロキシです。</p> <p>リソースに対して、いつでもアクセスを閉じられるようにしたいとしましょう。</p> <p>その方法としては、リソースをトラップをしない取り消し可能なプロキシでラップすることです。このようなプロキシはオブジェクトへ操作を転送しつつ、いつでもそれを無効にすることができます。</p> <p>構文は次の通りです:</p> <div id="q8wvx64mds" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let {proxy, revoke} = Proxy.revocable(target, handler)</code></pre> </div> </div> </div><p>この呼び出しは <code>proxy</code> と無効にするために <code>revoke</code> 関数を持つオブジェクトを返します。</p> <p>例:</p> <div id="5uu6lrkr0n" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let object = { data: "Valuable data" }; let {proxy, revoke} = Proxy.revocable(object, {}); // オブジェクトの代わりにプロキシをどこかに渡します alert(proxy.data); // Valuable data // 後で次のようにします revoke(); // すると、プロキシは機能しなくなります(無効化されました) alert(proxy.data); // Error</code></pre> </div> </div> </div><p><code>revoke()</code> 呼び出しは、プロキシからターゲットオブジェクトへのすべての内部参照を削除します。これにより繋がりがなくなります。</p> <p>初期状態で、<code>revoke</code> は <code>proxy</code> とは別なので、現在のスコープに <code>revoke</code> を残したまま、<code>proxy</code> を渡すことが可能です。</p> <p><code>proxy.revoke = revoke</code> と設定することで、proxy に <code>revoke</code> メソッドをバインドすることもできます。</p> <p>別の選択肢は、<code>WeakMap</code> を作成し、キーとして <code>proxy</code> を、値として対応する <code>revoke</code> をもたせることです。これで、簡単に proxy に対する <code>revoke</code> を見つけることができます。</p> <div id="knzci18j4d" data-trusted="1" class="code-example" data-highlight="[{"start":0,"end":0}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let revokes = new WeakMap(); let object = { data: "Valuable data" }; let {proxy, revoke} = Proxy.revocable(object, {}); revokes.set(proxy, revoke); // ..later in our code.. revoke = revokes.get(proxy); revoke(); alert(proxy.data); // Error (revoked)</code></pre> </div> </div> </div><p>ここで <code>Map</code> の代わりに <code>WeakMap</code> を使用しているのは、ガベージコレクションをブロックしないようにするためです。proxy オブジェクトが “到達不可能” になった(e.g それを参照する変数がなくなった)場合、<code>WeakMap</code> を利用すると、不要になった <code>revoke</code> を一緒にメモリ上から削除することができます。</p> <h2><a class="main__anchor" name="ref-700" href="#ref-700">リファレンス</a></h2><ul> <li>仕様: <a href="https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots">Proxy</a>.</li> <li>MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy</a>.</li> </ul> <h2><a class="main__anchor" name="ref-701" href="#ref-701">サマリ</a></h2><p><code>Proxy</code> はオブジェクトのラッパーであり、操作をオブジェクトへ転送し、必要に応じてその一部をトラップします。</p> <p>クラスや関数を含め、あらゆる種類のオブジェクトをラップすることができます。</p> <p>構文:</p> <div id="oole3l698v" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let proxy = new Proxy(target, { /* traps */ });</code></pre> </div> </div> </div><p>…それ以降はどこでも <code>target</code> の代わりに <code>proxy</code> を使う必要があります。プロキシは独自のプロパティやメソッドは持っていません。トラップが指定されていれば操作をトラップし、そうでなければ <code>target</code> オブジェクトに転送します。</p> <p>以下をトラップすることができます:</p> <ul> <li>プロパティ(存在しないものも含む)の読み取り(<code>get</code>)、書き込み(<code>set</code>)、削除(<code>deleteProperty</code>)</li> <li>関数呼び出し(<code>apply</code> トラップ)</li> <li><code>new</code> 演算子(<code>construct</code> トラップ)</li> <li>その他多くのトラップ(完全なリストはこの記事の冒頭と <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">docs</a>にあります。)</li> </ul> <p>これにより、“仮想の” プロパティやメソッドを作成したり、デフォルト値、オブザーバブルオブジェクト、関数デコレータなど様々なものを実装することができます。</p> <p>また、異なるプロキシで複数回オブジェクトをラップし、機能の様々な側面でオブジェクトデコレートすることも可能です。</p> <p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect">Reflect</a> API は <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy</a> を補完するためのものとして設計されています。すべての <code>Proxy</code> トラップに対して、同じ引数を持つ <code>Reflect</code> 呼び出しがあります。これらを使用してターゲットオブジェクトに転送する必要があります。</p> <p>プロキシにはいくつか制限があります:</p> <ul> <li>組み込みのオブジェクトには “内部スロット” があり、それらへのアクセスはプロキシすることはできません。上記の回避策を参照してください。</li> <li>プライベートクラスフィールドにも同じことが当てはまります。それらは内部的にはスロットを使用して実装されているため、プロキシされたメソッド呼び出しは、それらにアクセスするために <code>this</code> としてターゲットオブジェクトをもつ必要があります。</li> <li>オブジェクトの等価評価 <code>===</code> はインターセプトできません。</li> <li>パフォーマンス: ベンチマークはエンジンによりますが、通常、最も単純なプロキシを使用したプロパティへのアクセスするにも数倍時間がかかります。しかし実際にそれが問題になるのは一部の “ボトルネック” オブジェクトのみです。</li> </ul> </div></article><div class="tasks formatted"><h2 class="tasks__title" id="tasks"><a class="tasks__title-anchor main__anchor main__anchor main__anchor_noicon" href="#tasks">タスク</a></h2><div class="task tasks__task"><div class="task__header"><div class="task__title-wrap"><h3 class="task__title"><a class="main__anchor" href="#ref-702" name="ref-702">存在しないプロパティの読み取りエラー</a></h3><a class="task__open-link" href="/task/error-nonexisting" target="_blank"></a></div><div class="task__header-note"></div><div class="task__content"><div class="task__formatted"><p>通常、存在しないプロパティへの参照をすると <code>undefined</code> が返ってきます。</p> <p>代わりに、存在しないプロパティへの参照時にはエラーをスローするようなプロキシを作成してください。</p> <p>これはプログラミングのミスを早期に検出するのに便利です。</p> <p>オブジェクト <code>target</code> を取り、この機能を追加するプロキシを返す関数 <code>wrap(target)</code> を実装してください。</p> <p>次のように動作するようにしてください:</p> <div id="fqy7sij8tk" data-trusted="1" class="code-example" data-highlight="[{"start":13,"end":13},{"start":6,"end":6}]"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { name: "John" }; function wrap(target) { return new Proxy(target, { /* your code */ }); } user = wrap(user); alert(user.name); // John alert(user.age); // Error: Property doesn't exist</code></pre> </div> </div> </div></div><button class="task__solution" type="button">解答</button><div class="task__answer"><div class="task__answer-content"><div class="formatted"><div id="x53z1co0od" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let user = { name: "John" }; function wrap(target) { return new Proxy(target, { get(target, prop, receiver) { if (prop in target) { return Reflect.get(target, prop, receiver); } else { throw new ReferenceError(`Property doesn't exist: "${prop}"`) } } }); } user = wrap(user); alert(user.name); // John alert(user.age); // Error: Property doesn't exist</code></pre> </div> </div> </div></div></div><button class="close-button task__answer-close" type="button" title="閉じる"></button></div></div></div></div><div class="task tasks__task"><div class="task__header"><div class="task__title-wrap"><h3 class="task__title"><a class="main__anchor" href="#ref-703" name="ref-703">Accessing array[-1]</a></h3><a class="task__open-link" href="/task/array-negative" target="_blank"></a></div><div class="task__header-note"></div><div class="task__content"><div class="task__formatted"><p>プログラム言語によっては、負の値を使って配列要素にアクセスすることが可能で、この場合は末尾から数えられます。</p> <p>このようになります。</p> <div id="wrj6ub2aw5" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let array = [1, 2, 3]; array[-1]; // 3, 最後の要素 array[-2]; // 2, 最後から1つ前 array[-3]; // 1, 最後から2つ前</code></pre> </div> </div> </div><p>つまり、<code>array[-N]</code> は <code>array[array.length - N]</code> と同じです。</p> <p>この挙動を実装するプロキシを作成しましょう。</p> <p>次のように動作します:</p> <div id="dwzz01epm5" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let array = [1, 2, 3]; array = new Proxy(array, { /* your code */ }); alert( array[-1] ); // 3 alert( array[-2] ); // 2 // 他の配列の機能は "そのまま" 動作すべきです</code></pre> </div> </div> </div></div><button class="task__solution" type="button">解答</button><div class="task__answer"><div class="task__answer-content"><div class="formatted"><div id="j6ae7sjzmu" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let array = [1, 2, 3]; array = new Proxy(array, { get(target, prop, receiver) { if (prop < 0) { // arr[1] のようにアクセスしても // prop は文字列なので、数値に変換する必要があります prop = +prop + target.length; } return Reflect.get(target, prop, receiver); } }); alert(array[-1]); // 3 alert(array[-2]); // 2</code></pre> </div> </div> </div></div></div><button class="close-button task__answer-close" type="button" title="閉じる"></button></div></div></div></div><div class="task tasks__task"><div class="task__header"><div class="task__title-wrap"><h3 class="task__title"><a class="main__anchor" href="#ref-704" name="ref-704">Observable</a></h3><a class="task__open-link" href="/task/observable" target="_blank"></a></div><div class="task__header-note"></div><div class="task__content"><div class="task__formatted"><p>プロキシを返すことで、“オブジェクトを監視可能にする” 関数 <code>makeObservable(target)</code> を作成してください。</p> <p>このように動作します:</p> <div id="eaog8dvgdx" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>function makeObservable(target) { /* your code */ } let user = {}; user = makeObservable(user); user.observe((key, value) => { alert(`SET ${key}=${value}`); }); user.name = "John"; // alerts: SET name=John</code></pre> </div> </div> </div><p>つまり、<code>makeObservable</code> により返却されるオブジェクトは元のオブジェクトのように見えますが、任意のプロパティ変更時に呼び出される <code>handler</code> 関数をセットするメソッド <code>observe(handler)</code> を持ちます。</p> <p>プロパティを変更したときはいつでもプロパティの名前と値と一緒に <code>handler(key, value)</code> が呼ばれます。</p> <p>P.S. このタスクでは、プロパティの書き込みにだけ注目してください。他の操作も同様の方法で実装することはできます。</p> </div><button class="task__solution" type="button">解答</button><div class="task__answer"><div class="task__answer-content"><div class="formatted"><p>解決策は2つのパートで構成されます:</p> <ol> <li><code>.observe(handler)</code> が呼ばれたときは、後で <code>handler</code> が呼び出せるように、ハンドラをどこかに覚えておく必要があります。シンボルをプロパティのキーとして使用することで、ハンドラをオブジェクトに格納できます。</li> <li>変更時にハンドラを呼ぶための <code>set</code> トラップを持つプロキシが必要です。</li> </ol> <div id="awytqj2btp" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="実行" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="サンドボックで開く" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>let handlers = Symbol('handlers'); function makeObservable(target) { // 1. ハンドラの格納場所の初期化 target[handlers] = []; // 後々の呼び出しのため、配列にハンドラ関数を格納 target.observe = function(handler) { this[handlers].push(handler); }; // 2. 変更を処理するプロキシを作成 return new Proxy(target, { set(target, property, value, receiver) { let success = Reflect.set(...arguments); // 操作をオブジェクトに転送 if (success) { // プロパティの設定でエラーがなければ // すべてのハンドラを呼び出す target[handlers].forEach(handler => handler(property, value)); } return success; } }); } let user = {}; user = makeObservable(user); user.observe((key, value) => { alert(`SET ${key}=${value}`); }); user.name = "John";</code></pre> </div> </div> </div></div></div><button class="close-button task__answer-close" type="button" title="閉じる"></button></div></div></div></div></div></div><div class="page__nav-wrap"><a class="page__nav page__nav_prev" href="/js-misc" data-tooltip="その他"><span class="page__nav-text"><span class="page__nav-text-shortcut"></span></span><span class="page__nav-text-alternate">前のレッスン</span></a><a class="page__nav page__nav_next" href="/eval" data-tooltip="Eval: コード文字列を実行する"><span class="page__nav-text"><span class="page__nav-text-shortcut"></span></span><span class="page__nav-text-alternate">次のレッスン</span></a></div><div class="article-tablet-foot tablet-only"><div class="article-tablet-foot__layout"><div class="share-icons"><span class="share-icons__title">シェア</span><a class="share share_tw" href="https://twitter.com/share?url=https%3A%2F%2Fja.javascript.info%2Fproxy" rel="nofollow"></a><a class="share share_fb" href="https://www.facebook.com/sharer/sharer.php?s=100&p%5Burl%5D=https%3A%2F%2Fja.javascript.info%2Fproxy" rel="nofollow"></a></div><div class="article-tablet-foot__map"><a class="map" href="/tutorial/map" data-action="tutorial-map"><span class="map__text">チュートリアルマップ</span></a></div></div></div><div class="comments formatted" id="comments"><div class="comments__header"><h2 class="comments__header-title"><a href="#comments" name="comments">コメント</a></h2><div class="comments__read-before"><span class="comments__read-before-link">コメントをする前に読んでください…</span><div class="comments__read-before-popup"><div class="comments__read-before-popup-i"><ul><li>自由に記事への追加や質問を投稿をしたり、それらに回答してください。</li><li>数語のコードを挿入するには、<code><code></code> タグを使ってください。複数行の場合は <code><pre></code> を、10行を超える場合にはサンドボックスを使ってください(<a href='https://plnkr.co/edit/?p=preview'>plnkr</a>, <a href='https://jsbin.com'>JSBin</a>, <a href='http://codepen.io'>codepen</a>…)。</li><li>記事の中で理解できないことがあれば、詳しく説明してください。</li></ul></div></div></div></div><div id="disqus_thread"></div><script>var disqus_config = function() { if (!this.page) this.page = {}; Object.assign(this.page, {"url":"https:\/\/ja.javascript.info\/proxy","identifier":"\/proxy"}); };</script><script>var disqus_shortname = "ja-javascript-info";</script><script>var disqus_enabled = true;</script></div></script></main></div><div class="sidebar page__sidebar sidebar sidebar_sticky-footer"><button class="sidebar__toggle" data-sidebar-toggle></button><a class="map" href="/tutorial/map" data-action="tutorial-map" data-tooltip="チュートリアルマップ"></a><div class="sidebar__inner"><div class="sidebar__content"><div class="sidebar__section"><h4 class="sidebar__section-title">チャプター</h4><nav class="sidebar__navigation"><ul class="sidebar__navigation-links"><li class="sidebar__navigation-link"><a class="sidebar__link" href="/js-misc">その他</a></li></ul></nav></div><div class="sidebar__section"><h4 class="sidebar__section-title">レッスンナビゲーション</h4><nav class="sidebar__navigation"><ul class="sidebar__navigation-links"><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-686">Proxy</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-687">“get” トラップでのデフォルト値</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-688">“set” トラップでのバリデーション</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-689">“ownKeys” と “getOwnPropertyDescriptor” によるイテレーション</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-690">“deleteProperty” 及び他のトラップで保護されたプロパティ</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-691">“has” トラップを使用した “範囲内”</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-692">Wrapping functions: “apply”</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-693">Reflect</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-695">プロキシの制限</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-699">取り消し可能(revocable)なプロキシ</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-700">リファレンス</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#ref-701">サマリ</a></li></ul></nav></div><div class="sidebar__section"><nav class="sidebar__navigation"><ul class="sidebar__navigation-links"><li class="sidebar__navigation-link"><a class="sidebar__link" href="#tasks">タスク (3)</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#comments">コメント</a></li></ul></nav></div><div class="sidebar__section"><div class="sidebar__section-title">シェア</div><a class="share share_tw sidebar__share" href="https://twitter.com/share?url=https%3A%2F%2Fja.javascript.info%2Fproxy" rel="nofollow"></a><a class="share share_fb sidebar__share" href="https://www.facebook.com/sharer/sharer.php?s=100&p[url]=https%3A%2F%2Fja.javascript.info%2Fproxy" rel="nofollow"></a></div><div class="sidebar__section"><a class="sidebar__link" href="https://github.com/javascript-tutorial/ja.javascript.info/blob/master/1-js/99-js-misc/01-proxy" rel="nofollow">GitHubで編集</a></div></div></div></div></div></div><div class="page-footer"><ul class="page-footer__list"><li class="page-footer__item page-footer__item_copy">© 2007—2025 Ilya Kantor</li><li class="page-footer__item page-footer__item_about"><a class="page-footer__link" href="/about">プロジェクトについて</a></li><li class="page-footer__item page-footer__item_contact"><a class="page-footer__link" href="/about#contact-us">コンタクト</a></li></ul></div></body></html>