{
    "componentChunkName": "component---src-templates-blog-post-jsx",
    "path": "/post/how-to-implement-dirty-check-and-method-missing-with-nodejs/",
    "result": {"data":{"site":{"siteMetadata":{"title":"WEB EGG","author":"Leko - CTO at Yuimedi"}},"markdownRemark":{"id":"add3c127-dd0d-58cf-b159-92e96ed4f108","excerpt":"過去にphpのマジックメソッドを使ってRailsのfind_all_by_*メソッドを実装してみる | WEB EGGという記事を書いたのですが、Node.jsでもProxyの登場により、似たようなことができるのでは？ と思ったので試してみました。 今回の題材は、同じくRailsのActiveRecord…","html":"<p>過去に<a href=\"/post/how-to-implement-find-all-by-with-php-magic-method/\">phpのマジックメソッドを使ってRailsのfind_all_by_*メソッドを実装してみる | WEB EGG</a>という記事を書いたのですが、Node.jsでも<a href=\"https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Proxy</a>の登場により、似たようなことができるのでは？ と思ったので試してみました。</p>\n<p>今回の題材は、同じくRailsのActiveRecordから、<a href=\"http://api.rubyonrails.org/classes/ActiveModel/Dirty.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">ActiveModel::Dirty</a>モジュールです。</p>\n<div class=\"gatsby-highlight\" data-language=\"ruby\"><pre class=\"language-ruby\"><code class=\"language-ruby\">person <span class=\"token operator\">=</span> <span class=\"token constant\">Person</span><span class=\"token punctuation\">.</span><span class=\"token keyword\">new</span>\nperson<span class=\"token punctuation\">.</span>changed<span class=\"token operator\">?</span> <span class=\"token comment\"># => false</span>\n\nperson<span class=\"token punctuation\">.</span>name <span class=\"token operator\">=</span> <span class=\"token string\">'Bob'</span>\nperson<span class=\"token punctuation\">.</span>changed<span class=\"token operator\">?</span>       <span class=\"token comment\"># => true</span>\nperson<span class=\"token punctuation\">.</span>name_changed<span class=\"token operator\">?</span>  <span class=\"token comment\"># => true</span>\nperson<span class=\"token punctuation\">.</span>name_was       <span class=\"token comment\"># => nil</span>\nperson<span class=\"token punctuation\">.</span>name_change    <span class=\"token comment\"># => [nil, \"Bob\"]</span>\nperson<span class=\"token punctuation\">.</span>name <span class=\"token operator\">=</span> <span class=\"token string\">'Bill'</span>\nperson<span class=\"token punctuation\">.</span>name_change    <span class=\"token comment\"># => [nil, \"Bill\"]</span></code></pre></div>\n<p>こんな感じに変更を検知するためのマジックメソッド、ユーティリティが加わるモジュールだそうです。</p>\n<p>昔であれば<a href=\"http://backbonejs.org/#Model-changed\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Backbone.jsのモデル</a>が似たような仕組みを提供していました。<br>\nですが、あれば独自のセッタを提供しており、それを利用しているから変更が検知できるという仕組みです。 <strong>いわば白魔術です</strong></p>\n<p>今回は、 <strong>独自のセッタ</strong> を提供せず、普通にオブジェクト操作しているだけで変更検知ができちゃう機能の実装を目指します。<br>\n白魔術に対して言うなれば、黒魔術です。</p>\n<p>ちなみに使用しているNode.jsのバージョンはv6.1.0です。</p>\n<!--more-->\n<h2 id=\"簡単な設計\" style=\"position:relative;\"><a href=\"#%E7%B0%A1%E5%8D%98%E3%81%AA%E8%A8%AD%E8%A8%88\" aria-label=\"簡単な設計 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>簡単な設計</h2>\n<p>モジュールは高階関数として作成し、使用する際はdecoratorとして利用できるようにします。<br>\nなので継承関係によらず、任意のクラスに対して適応可能です。<br>\nざっくりしたイメージとしてはPHPでいうところの<code>trait</code>、Rubyでいうところの<code>include</code>相当だと思ってもらえればいいと思います</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\">@DirtyCheckable\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">Profile</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">constructor</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">name</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>name <span class=\"token operator\">=</span> name\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>コンストラクタの形式は問いません。thisに何かセットされていればそれを利用できるようにします。 こんな感じで利用できる<code>DirtyCheckable</code>関数を実装していきます</p>\n<p>完成済みのコードは<a href=\"https://gist.github.com/Leko/36dd864f87d0e2e61745f7869e2a8731\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">gist</a>に上げてあります。</p>\n<h2 id=\"decoratorの挙動\" style=\"position:relative;\"><a href=\"#decorator%E3%81%AE%E6%8C%99%E5%8B%95\" aria-label=\"decoratorの挙動 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>decoratorの挙動</h2>\n<p>decoratorはReactで<a href=\"https://facebook.github.io/react/docs/higher-order-components.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">High Order Components</a>なんて言われて流行ってますが、要は昔からある関数型言語のアプローチのひとつ、高階関数です。</p>\n<blockquote>\n<p>— <a href=\"http://qiita.com/To_BB/items/c9ce3391495f2ea9eb31\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">【エンジニア初心者向け】高階関数入門(Javascript) – Qiita</a></p>\n</blockquote>\n<p>一応、先程のコードをjsのコードにするとこんな感じになります</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> Profile <span class=\"token operator\">=</span> <span class=\"token function\">DirtyCheckable</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">class</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">constructor</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">name</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>name <span class=\"token operator\">=</span> name\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>DirtyCheckableの要件は、クラスを受け取りクラスを返す関数になります。 実装イメージとしては、以下のような感じになります。</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">function</span> <span class=\"token function\">DirtyCheckable</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">cls</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">extends</span> cls <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// ...</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>継承の逆？ と言えば伝わるんでしょうか。<br>\n渡されたクラスを親クラスにとる無名クラスを作成して返す感じです。</p>\n<h2 id=\"proxyの挙動\" style=\"position:relative;\"><a href=\"#proxy%E3%81%AE%E6%8C%99%E5%8B%95\" aria-label=\"proxyの挙動 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Proxyの挙動</h2>\n<p>Proxy自体の説明は<a href=\"https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">MDN</a>を見ればだいたいわかると思います。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> obj <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Proxy</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> <span class=\"token function\">set</span> <span class=\"token punctuation\">(</span>instance<span class=\"token punctuation\">,</span> prop<span class=\"token punctuation\">,</span> value<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span> console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>prop<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">=</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span><span class=\"token constant\">JSON</span><span class=\"token punctuation\">.</span><span class=\"token function\">stringify</span><span class=\"token punctuation\">(</span>value<span class=\"token punctuation\">)</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span> instance<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> value <span class=\"token punctuation\">}</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\nobj<span class=\"token punctuation\">.</span>hoge <span class=\"token operator\">=</span> <span class=\"token number\">1</span></code></pre></div>\n<p>実行すると<code>hoge=1</code>と出力されたと思います。<br>\nこんな感じで、ただのオブジェクト操作をフックすることが可能になります。</p>\n<p>setの中身を実装することで、dirty checkを実装できます。 同様にgetの中身を実装することで、method missingも実装できます。</p>\n<h2 id=\"クラスをproxyする\" style=\"position:relative;\"><a href=\"#%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92proxy%E3%81%99%E3%82%8B\" aria-label=\"クラスをproxyする permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>クラスをProxyする</h2>\n<ul>\n<li>コンストラクタの形式を制限しないように可変長で受け取って可変長で渡す</li>\n<li>Proxyのインスタンスを返す</li>\n</ul>\n<p>コードは以下のような感じです。</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// dirty checkするためのクラス</span>\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">DirtyChecker</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// TODO: Implement dirty check</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token comment\">// get, setをフックするもの</span>\n<span class=\"token keyword\">const</span> observer <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">get</span> <span class=\"token punctuation\">(</span>instance<span class=\"token punctuation\">,</span> prop<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// TODO: Implement method missing</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token function\">set</span> <span class=\"token punctuation\">(</span>instance<span class=\"token punctuation\">,</span> prop<span class=\"token punctuation\">,</span> value<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// TODO: Implement dirty check</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token comment\">// 与えられたオブジェクトに応じてDirtyCheckerのインスタンスを作成する</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">createDirtyCheckers</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">obj</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> dirties <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> prop <span class=\"token keyword\">of</span> Object<span class=\"token punctuation\">.</span><span class=\"token function\">getOwnPropertyNames</span><span class=\"token punctuation\">(</span>obj<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">DirtyChecker</span><span class=\"token punctuation\">(</span>obj<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">return</span> dirties\n<span class=\"token punctuation\">}</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">DirtyCheckable</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">cls</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">extends</span> cls <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">constructor</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\"><span class=\"token operator\">...</span>args</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">super</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>args<span class=\"token punctuation\">)</span>\n      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>dirties <span class=\"token operator\">=</span> <span class=\"token function\">createDirtyCheckers</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">)</span>\n      <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Proxy</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">,</span> observer<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>上記のコードをベースに実装を続けます。</p>\n<h2 id=\"nodejsでdirty-check\" style=\"position:relative;\"><a href=\"#nodejs%E3%81%A7dirty-check\" aria-label=\"nodejsでdirty check permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Node.jsでdirty check</h2>\n<p>早速実装します。<code>DirtyChecker</code>はただのユーティリティなので実装は<a href=\"https://gist.github.com/Leko/36dd864f87d0e2e61745f7869e2a8731#file-dirtycheckable-js-L39\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">gist</a>を御覧ください。<br>\n先述のコードの<code>observer</code>のsetを実装します。<br>\n<code>instance</code>は呼び出し元のインスタンスを指します。</p>\n<p>なので、<code>this.dirties</code> = <code>instance.dirties</code>です。<br>\nということで、<code>DirtyChecker#set</code>をコールするだけです。</p>\n<p><code>instance[prop] = value</code>を忘れるとインスタンスに値が反映されないのでご注意下さい。</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token function\">set</span> <span class=\"token punctuation\">(</span>instance<span class=\"token punctuation\">,</span> prop<span class=\"token punctuation\">,</span> value<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  instance<span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> instance<span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span> <span class=\"token operator\">||</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">DirtyChecker</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  instance<span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>value<span class=\"token punctuation\">)</span>\n  instance<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> value\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>これで変更検知の仕組みは完成したので、後はユーティリティを実装します。</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">DirtyCheckable</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">cls</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">extends</span> cls <span class=\"token punctuation\">{</span>\n\n    <span class=\"token comment\">// ...</span>\n\n    <span class=\"token function\">changed</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> prop <span class=\"token keyword\">in</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">changed</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">return</span> <span class=\"token boolean\">true</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">}</span>\n      <span class=\"token keyword\">return</span> <span class=\"token boolean\">false</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token function\">changes</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">const</span> changes <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\n      <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> prop <span class=\"token keyword\">in</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">changed</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n          changes<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">changes</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">}</span>\n      <span class=\"token keyword\">return</span> changes\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>試してみます。</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\">@DirtyCheckable\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">Profile</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">constructor</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">name</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>name <span class=\"token operator\">=</span> name\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">const</span> hoge <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Profile</span><span class=\"token punctuation\">(</span><span class=\"token string\">'John'</span><span class=\"token punctuation\">)</span>\n\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'hoge.name:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'changes:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">changes</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'changed:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">changed</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\nhoge<span class=\"token punctuation\">.</span>name <span class=\"token operator\">=</span> <span class=\"token string\">'Tom'</span>\n\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'hoge.name:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'changes:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">changes</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'changed:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">changed</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>実行結果は</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">$ babel-node index.js\nhoge.name: John\nchanges: {}\nchanged: false\nhoge.name: Tom\nchanges: { name: [ 'John', 'Tom' ] }\nchanged: true</code></pre></div>\n<p>いい感じです。<br>\n各プロパティごとの<code>*Was</code>, <code>*Changed</code>, <code>*Change</code>メソッドはmethod missingを利用して実装します。</p>\n<h2 id=\"nodejsでmethod-missing\" style=\"position:relative;\"><a href=\"#nodejs%E3%81%A7method-missing\" aria-label=\"nodejsでmethod missing permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Node.jsでmethod missing</h2>\n<p>今度は<code>observer</code>のgetを実装していきます</p>\n<ul>\n<li>もし定義済のプロパティならそれを返す</li>\n<li>未定義の値が来たらmethod missingのフォールバック処理へ以降</li>\n<li>余計なサフィックスを除去し、本来のプロパティ名をフォールバック処理へ渡す</li>\n</ul>\n<p>という感じです。</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> fallbackSuffixes <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">Changed</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">instance<span class=\"token punctuation\">,</span> prop</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> instance<span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">changed</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  <span class=\"token function\">Change</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">instance<span class=\"token punctuation\">,</span> prop</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> instance<span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">changes</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  <span class=\"token function\">Was</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">instance<span class=\"token punctuation\">,</span> prop</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> instance<span class=\"token punctuation\">.</span>dirties<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">was</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">const</span> observer <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">get</span> <span class=\"token punctuation\">(</span>instance<span class=\"token punctuation\">,</span> prop<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span> instance<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span> <span class=\"token operator\">!==</span> <span class=\"token string\">'undefined'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">return</span> instance<span class=\"token punctuation\">[</span>prop<span class=\"token punctuation\">]</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> suffix <span class=\"token keyword\">in</span> fallbackSuffixes<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>prop<span class=\"token punctuation\">.</span><span class=\"token function\">endsWith</span><span class=\"token punctuation\">(</span>suffix<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">const</span> propName <span class=\"token operator\">=</span> prop<span class=\"token punctuation\">.</span><span class=\"token function\">slice</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span>suffix<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>instance<span class=\"token punctuation\">[</span>propName<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">return</span> fallbackSuffixes<span class=\"token punctuation\">[</span>suffix<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">bind</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span> instance<span class=\"token punctuation\">,</span> propName<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  <span class=\"token comment\">// ...</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>完成です。ここまでのコードを纏めて実行してみると、</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\">@DirtyCheckable\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">Profile</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">constructor</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">name</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>name <span class=\"token operator\">=</span> name\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">const</span> hoge <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Profile</span><span class=\"token punctuation\">(</span><span class=\"token string\">'John'</span><span class=\"token punctuation\">)</span>\n\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'hoge.name:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'nameWas:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">nameWas</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'nameChanged:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">nameChanged</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'nameChange:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">nameChange</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\nhoge<span class=\"token punctuation\">.</span>name <span class=\"token operator\">=</span> <span class=\"token string\">'Tom'</span>\n\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'hoge.name:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'nameWas:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">nameWas</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'nameChanged:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">nameChanged</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">'nameChange:'</span><span class=\"token punctuation\">,</span> hoge<span class=\"token punctuation\">.</span><span class=\"token function\">nameChange</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span></code></pre></div>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">$ babel-node index.js\nhoge.name: John\nnameWas: John\nnameChanged: false\nnameChange: [ 'John', undefined ]\nhoge.name: Tom\nnameWas: John\nnameChanged: true\nnameChange: [ 'John', 'Tom' ]</code></pre></div>\n<p>いい感じです。これでdirty checkとmethod missingの実装が完了しました。</p>\n<h2 id=\"パフォーマンス測定\" style=\"position:relative;\"><a href=\"#%E3%83%91%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%B3%E3%82%B9%E6%B8%AC%E5%AE%9A\" aria-label=\"パフォーマンス測定 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>パフォーマンス測定</h2>\n<p>最後に気になるパフォーマンスですが、<a href=\"https://gist.github.com/Leko/36dd864f87d0e2e61745f7869e2a8731#file-benchmark-js\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">こんなコード</a>で比較してみます</p>\n<p>100,000回同じ処理をしてみてどれくらいコスト差があるか比べてみました。</p>\n<table>\n<thead>\n<tr>\n<th>メソッド</th>\n<th>ProfileWithDirty</th>\n<th>Profile</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>new</td>\n<td>244 ms</td>\n<td>6 ms</td>\n</tr>\n<tr>\n<td>set</td>\n<td>29 ms</td>\n<td>5 ms</td>\n</tr>\n<tr>\n<td>get</td>\n<td>35 ms</td>\n<td>1 ms</td>\n</tr>\n<tr>\n<td>methodCall</td>\n<td>63 ms</td>\n<td>3 ms</td>\n</tr>\n</tbody>\n</table>\n<p>newが激遅いです。<br>\n他も優位な差が出ているものの、10万回で数十ms程度の差なら無視しても良いレベルではないでしょうか。</p>\n<h2 id=\"まとめ\" style=\"position:relative;\"><a href=\"#%E3%81%BE%E3%81%A8%E3%82%81\" aria-label=\"まとめ permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>まとめ</h2>\n<p>かなり愚直な方法で実装しているので、もっと早い実装がたくさんあると思います。 使いみちが色々あって面白いので、ぜひProxy利用してみて下さい。 ただしよほど丁寧に書かないと黒魔術化は必至なので、用法用量をお守りのうえお楽しみ下さい。</p>","timeToRead":8,"frontmatter":{"title":"Node.jsのProxyでdirty checkとmethod missingを実現してみる","tags":["JavaScript","Nodejs","Proxy","Ruby"],"date":"June 07, 2017","featuredImage":null}}},"pageContext":{"slug":"/how-to-implement-dirty-check-and-method-missing-with-nodejs/","previous":{"fields":{"slug":"/knowhow-of-circleci-1/"},"frontmatter":{"title":"CircleCI 1.0でDockerやdocker-composeを使用する際の制限と気をつけること","tags":["CI","CircleCI","Docker","Docker compose"]}},"next":{"fields":{"slug":"/es2017-object-syntax/"},"frontmatter":{"title":"シンプルすぎて難解？昨今のNode.jsのオブジェクト周りの構文をまとめてみた","tags":["JavaScript","Nodejs"]}}}},
    "staticQueryHashes": ["2585454260","2954598359"]}