{"id":3381,"date":"2024-08-09T13:54:47","date_gmt":"2024-08-09T18:54:47","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=3381"},"modified":"2024-08-09T13:54:48","modified_gmt":"2024-08-09T18:54:48","slug":"exploring-the-possibilities-of-native-javascript-decorators","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/exploring-the-possibilities-of-native-javascript-decorators\/","title":{"rendered":"Exploring the Possibilities of Native JavaScript Decorators"},"content":{"rendered":"\n<p>We&#8217;ve known it for a while now, but JavaScript is eventually getting native support for decorators. The proposal is in&nbsp;<a href=\"https:\/\/github.com\/tc39\/proposal-decorators\">stage 3<\/a>&nbsp;\u2014 it\u2019s inevitable! I&#8217;m just coming around to explore the feature, and I&#8217;m kinda kicking myself for waiting so long, because I\u2019m finding it to be tremendously helpful. Let\u2019s spend some time exploring it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-pattern-vs-the-feature\">The Pattern vs The Feature<\/h2>\n\n\n\n<p>It&#8217;s probably worth clarifying what&#8217;s meant by a &#8220;decorator.&#8221; Most of the time, people are talking about one of two things:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>The decorator<\/strong>&nbsp;<em><strong>design pattern<\/strong><\/em><\/h3>\n\n\n\n<p>This is the&nbsp;<a href=\"https:\/\/en.wikipedia.org\/wiki\/Decorator_pattern\">higher-level concept<\/a>&nbsp;of augmenting or extending a function&#8217;s behavior by &#8220;decorating&#8221; it. Logging is a common example. You might want to know&nbsp;<em>when<\/em>&nbsp;and&nbsp;<em>with what<\/em>&nbsp;parameters it&#8217;s called, so you wrap it with another function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">add<\/span>(<span class=\"hljs-params\">a, b<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> a + b;\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">log<\/span>(<span class=\"hljs-params\">func<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n    <span class=\"hljs-built_in\">console<\/span>.log(\n      <span class=\"hljs-string\">`method: <span class=\"hljs-subst\">${func.name}<\/span> | `<\/span>,\n      <span class=\"hljs-string\">`arguments: <span class=\"hljs-subst\">${&#91;...args].join(<span class=\"hljs-string\">\", \"<\/span>)}<\/span>`<\/span>\n    );\n    <span class=\"hljs-keyword\">return<\/span> func.call(<span class=\"hljs-keyword\">this<\/span>, ...args);\n  };\n}\n\n<span class=\"hljs-keyword\">const<\/span> addWithLogging = log(add);\n\naddWithLogging(<span class=\"hljs-number\">1<\/span>, <span class=\"hljs-number\">2<\/span>);\n<span class=\"hljs-comment\">\/\/ adding 1 2<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>There&#8217;s no new language-specific feature here. One function simply accepts another as an argument and returns a new, souped-up version. The original function has been&nbsp;<em>decorated<\/em>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Decorators as a<\/strong>&nbsp;<em><strong>feature of the language<\/strong><\/em><\/h3>\n\n\n\n<p>The decorator feature is a more tangible manifestation of the pattern. It&#8217;s possible you&#8217;ve seen an older, unofficial version of this before. We&#8217;ll keep using the logging example from above, but we&#8217;ll first need to refactor a bit because language-level decorators can only be used on class methods, fields, and on classes themselves.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ The \"old\" decorator API:<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">log<\/span>(<span class=\"hljs-params\">target, key, descriptor<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> originalMethod = descriptor.value;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  descriptor.value = <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.log(\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-string\">`method: <span class=\"hljs-subst\">${originalMethod.name}<\/span> | `<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-string\">`arguments: <span class=\"hljs-subst\">${&#91;...args].join(<span class=\"hljs-string\">\", \"<\/span>)}<\/span>`<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    );\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> originalMethod.apply(<span class=\"hljs-keyword\">this<\/span>, args);\n<\/span><\/span><span class='shcb-loc'><span>  };\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> descriptor;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Calculator<\/span> <\/span>{\n<\/span><\/span><mark class='shcb-loc'><span>  @log <span class=\"hljs-comment\">\/\/ &lt;-- Decorator applied here.<\/span>\n<\/span><\/mark><span class='shcb-loc'><span>  add(a, b) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> a + b;\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">new<\/span> Calculator().add(<span class=\"hljs-number\">1<\/span>, <span class=\"hljs-number\">2<\/span>); <span class=\"hljs-comment\">\/\/ method: add | arguments: 1, 2<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Despite being non-standard, there are a number of popular, mature libraries out there that have used this implementation.&nbsp;<a href=\"https:\/\/typeorm.io\/\">TypeORM<\/a>,&nbsp;<a href=\"https:\/\/angular.io\/features\">Angular<\/a>, and&nbsp;<a href=\"https:\/\/docs.nestjs.com\/controllers\">NestJS<\/a>&nbsp;are just a few of the big ones. And I&#8217;m glad they have. It&#8217;s made building applications with them feel cleaner, more expressive, and easier to maintain.<\/p>\n\n\n\n<p>But because it&#8217;s non-standard, it could become problematic. For example,&nbsp;<a href=\"https:\/\/github.com\/babel\/babel\/issues\/8864#issuecomment-688535867\">there&#8217;s some nuance<\/a>&nbsp;between how it\u2019s implemented by Babel and TypeScript, which probably caused frustration for engineers moving between applications with different build tooling. Standardization would serve them well.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-slightly-different-official-api\">The Slightly Different Official API<\/h2>\n\n\n\n<p>Fortunately, both TypeScript (<a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/release-notes\/typescript-5-0.html#decorators\">as of v5<\/a>) and Babel (<a href=\"https:\/\/babeljs.io\/docs\/babel-plugin-proposal-decorators\">via plugin<\/a>) now support the TC39 version of the API, which is even simpler:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">log<\/span>(<span class=\"hljs-params\">func, context<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n    <span class=\"hljs-built_in\">console<\/span>.log(\n      <span class=\"hljs-string\">`method: <span class=\"hljs-subst\">${func.name}<\/span> | `<\/span>,\n      <span class=\"hljs-string\">`arguments: <span class=\"hljs-subst\">${&#91;...args].join(<span class=\"hljs-string\">\", \"<\/span>)}<\/span>`<\/span>\n    );\n\n    func.call(<span class=\"hljs-keyword\">this<\/span>, ...args);\n  };\n}\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Calculator<\/span> <\/span>{\n  @log\n  add(a, b) {\n    <span class=\"hljs-keyword\">return<\/span> a + b;\n  }\n}\n\n<span class=\"hljs-keyword\">new<\/span> Calculator().add(<span class=\"hljs-number\">1<\/span>, <span class=\"hljs-number\">2<\/span>); <span class=\"hljs-comment\">\/\/ method: add | arguments: 1, 2<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As you can see, there\u2019s much less of a learning curve, and it&#8217;s fully interchangeable with many functions that have been used as decorators until now. The only difference is that it\u2019s implemented with new syntax.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"exploring-the-use-cases\">Exploring the Use Cases<\/h2>\n\n\n\n<p>There\u2019s no shortage of scenarios in which this feature will be handy, but let\u2019s try out a couple that come to mind.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Debouncing &amp; Throttling<\/strong><\/h3>\n\n\n\n<p>Limiting the number of times an action occurs in a given amount of time is an age-old need on the web. Typically, that\u2019s meant reaching for a Lodash utility or rolling an implementation yourself.<\/p>\n\n\n\n<p>Think of a live search box. To prevent user experience issues and network load, you want to&nbsp;<em>debounce<\/em>&nbsp;those searches, only firing a request when the user has stopped typing for a period of time:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">debounce<\/span>(<span class=\"hljs-params\">func<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> timeout = <span class=\"hljs-literal\">null<\/span>;\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n    clearTimeout(timeout);\n\n    timeout = setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      func.apply(<span class=\"hljs-keyword\">this<\/span>, args);\n    }, <span class=\"hljs-number\">500<\/span>);\n  };\n}\n\n<span class=\"hljs-keyword\">const<\/span> debouncedSearch = debounce(search);\n\n<span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">'keyup'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">e<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ Will only fire after typing has stopped for 500ms.<\/span>\n  debouncedSearch(e.target.value);\n});  <\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But decorators can only be used on a class or its members, so let&#8217;s flesh out a better example. You&#8217;ve got a&nbsp;<code>ViewController<\/code>&nbsp;class with a method for handling&nbsp;<code>keyup<\/code>&nbsp;events:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ViewController<\/span> <\/span>{\n  <span class=\"hljs-keyword\">async<\/span> handleSearch(query) {\n    <span class=\"hljs-keyword\">const<\/span> results = <span class=\"hljs-keyword\">await<\/span> search(query);\n\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Update UI with:`<\/span>, results);\n  }\n}\n\n<span class=\"hljs-keyword\">const<\/span> controller = <span class=\"hljs-keyword\">new<\/span> ViewController();\n\ninput.addEventListener(<span class=\"hljs-string\">'keyup'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">e<\/span>) <\/span>{\n  controller.handleSearch(e.target.value);\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Using the&nbsp;<code>debounce()<\/code>&nbsp;method we wrote above, implementation would be clunky. Focusing in on the&nbsp;<code>ViewController<\/code>&nbsp;class itself:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ViewController<\/span> <\/span>{\n  handleSearch = debounce(<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">query<\/span>) <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> results = <span class=\"hljs-keyword\">await<\/span> search(query);\n\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Got results!`<\/span>, results);\n  });\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You not only need to wrap your&nbsp;<em>entire<\/em>&nbsp;method, but you also need to switch from defining a class method to an instance property set to the debounced version of that method. It\u2019s a little invasive.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Updating to a Native Decorator<\/h3>\n\n\n\n<p>Turning that&nbsp;<code>debounce()<\/code>&nbsp;function into an official decorator won&#8217;t take much. In fact, the way it&#8217;s already written fits the API perfectly: it accepts the original function and spits out the augmented version. So, all we need to do is apply it with the&nbsp;<code>@<\/code>&nbsp;syntax:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ViewController<\/span> <\/span>{\n<\/span><\/span><mark class='shcb-loc'><span>  @debounce\n<\/span><\/mark><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">async<\/span> handleSearch(query) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> results = <span class=\"hljs-keyword\">await<\/span> search(query);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Got results!`<\/span>, results);\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That&#8217;s all it takes \u2014 a single line \u2014 for the exact same result.<\/p>\n\n\n\n<p>We can also make the debouncing delay configurable by making&nbsp;<code>debounce()<\/code>&nbsp;accept a&nbsp;<code>delay<\/code>&nbsp;value and return a decorator itself:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-comment\">\/\/ Accept a delay:<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">debounce<\/span>(<span class=\"hljs-params\">delay<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> timeout = <span class=\"hljs-literal\">null<\/span>;\n\n  <span class=\"hljs-comment\">\/\/ Return the configurable decorator:<\/span>\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">value<\/span>) <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n      clearTimeout(timeout);\n\n      timeout = setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n        value.call(<span class=\"hljs-keyword\">this<\/span>, ...args);\n      }, delay);\n    };\n  };\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Using it just means calling our decorator wrapper as a function and passing the value:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ViewController<\/span> <\/span>{\n<\/span><\/span><mark class='shcb-loc'><span>  @debounce(<span class=\"hljs-number\">500<\/span>)\n<\/span><\/mark><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">async<\/span> handleSearch(query) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> results = <span class=\"hljs-keyword\">await<\/span> search(query);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Got results!`<\/span>, results);\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That\u2019s a lot of value for minimal code wrangling, especially support being provided by TypeScript and Babel \u2014 tools already well-integrated in our build processes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Memoization<\/strong><\/h3>\n\n\n\n<p>Whenever I think of great memoization that&#8217;s syntactically beautiful, Ruby first comes to mind. I&#8217;ve written about&nbsp;<a href=\"https:\/\/macarthur.me\/posts\/memoization-with-tap-in-ruby\/\">how elegant it is<\/a>&nbsp;in the past; the&nbsp;<code>||=<\/code>&nbsp;operator is all you really need:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Ruby\" data-shcb-language-slug=\"ruby\"><span><code class=\"hljs language-ruby\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">results<\/span><\/span>\n  @results <span class=\"hljs-params\">||<\/span>= calculate_results\n<span class=\"hljs-keyword\">end<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Ruby<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">ruby<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But with decorators, JavaScript&#8217;s making solid strides. Here\u2019s a simple implementation that caches the result of a method, and uses that value for any future invocations:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">memoize<\/span>(<span class=\"hljs-params\">func<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> cachedValue;\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n    <span class=\"hljs-comment\">\/\/ If it's been run before, return from cache.<\/span>\n    <span class=\"hljs-keyword\">if<\/span> (cachedValue) {\n      <span class=\"hljs-keyword\">return<\/span> cachedValue;\n    }\n\n    cachedValue = func.call(<span class=\"hljs-keyword\">this<\/span>, ...args);\n\n    <span class=\"hljs-keyword\">return<\/span> cachedValue;\n  };\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The nice thing about this is that each invocation of a decorator declares its own scope, meaning you can reuse it without risk of the&nbsp;<code>cachedValue<\/code>&nbsp;being overwritten with an unexpected value.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Student<\/span> <\/span>{\n  @memoize\n  calculateGPA() {\n    <span class=\"hljs-comment\">\/\/ Expensive computation...<\/span>\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-number\">3.9<\/span>;\n  }\n\n  @memoize\n  calculateACT() {\n    <span class=\"hljs-comment\">\/\/ Expensive computation...<\/span>\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-number\">34<\/span>;\n  }\n}\n\n<span class=\"hljs-keyword\">const<\/span> bart = <span class=\"hljs-keyword\">new<\/span> Student();\n\nbart.calculateGPA();\n<span class=\"hljs-built_in\">console<\/span>.log(bart.calculateGPA()); <span class=\"hljs-comment\">\/\/ from cache: 3.9<\/span>\n\nbart.calculateACT();\n<span class=\"hljs-built_in\">console<\/span>.log(bart.calculateACT()); <span class=\"hljs-comment\">\/\/ from cache: 34<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Going further, we could also memoize based on the parameters passed to a method:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">memoize<\/span>(<span class=\"hljs-params\">func<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ A place for each distinct set of parameters.<\/span>\n  <span class=\"hljs-keyword\">let<\/span> cache = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Map<\/span>();\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> key = <span class=\"hljs-built_in\">JSON<\/span>.stringify(args);\n\n    <span class=\"hljs-comment\">\/\/ This set of parameters has a cached value.<\/span>\n    <span class=\"hljs-keyword\">if<\/span> (cache.has(key)) {\n      <span class=\"hljs-keyword\">return<\/span> cache.get(key);\n    }\n\n    <span class=\"hljs-keyword\">const<\/span> value = func.call(<span class=\"hljs-keyword\">this<\/span>, ...args);\n\n    cache.set(key, value);\n\n    <span class=\"hljs-keyword\">return<\/span> value;\n  };\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now, regardless of parameter usage, memoization can become even more flexible:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Student<\/span> <\/span>{\n  @memoize\n  calculateRank(otherGPAs) {\n    <span class=\"hljs-keyword\">const<\/span> sorted = &#91;...otherGPAs].sort().reverse();\n\n    <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">let<\/span> i = <span class=\"hljs-number\">0<\/span>; i &lt;= sorted.length; i++) {\n      <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.calculateGPA() &gt; sorted&#91;i]) {\n        <span class=\"hljs-keyword\">return<\/span> i + <span class=\"hljs-number\">1<\/span>;\n      }\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-number\">1<\/span>;\n  }\n\n  @memoize\n  calculateGPA() {\n    <span class=\"hljs-comment\">\/\/ Expensive computation...<\/span>\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-number\">3.4<\/span>;\n  }\n}\n\n<span class=\"hljs-keyword\">const<\/span> bart = <span class=\"hljs-keyword\">new<\/span> Student();\n\nbart.calculateRank(&#91;<span class=\"hljs-number\">3.5<\/span>, <span class=\"hljs-number\">3.7<\/span>, <span class=\"hljs-number\">3.1<\/span>]); <span class=\"hljs-comment\">\/\/ fresh<\/span>\nbart.calculateRank(&#91;<span class=\"hljs-number\">3.5<\/span>, <span class=\"hljs-number\">3.7<\/span>, <span class=\"hljs-number\">3.1<\/span>]); <span class=\"hljs-comment\">\/\/ cached<\/span>\nbart.calculateRank(&#91;<span class=\"hljs-number\">3.5<\/span>]); <span class=\"hljs-comment\">\/\/ fresh<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That\u2019s cool, but it\u2019s also worth noting that you could run into issues if you&#8217;re dealing with parameters that can&#8217;t be serialized (<code>undefined<\/code>, objects with circular references, etc.). So, use it with some caution.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Memoizing Getters<\/strong><\/h3>\n\n\n\n<p>Since decorators can be used on more than just methods, a slight adjustment means we can memoize getters too. We just need to use&nbsp;<code>context.name<\/code>&nbsp;(the name of the getter) as the cache key:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">memoize<\/span>(<span class=\"hljs-params\">func, context<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> cache = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Map<\/span>();\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-keyword\">if<\/span> (cache.has(context.name)) {\n      <span class=\"hljs-keyword\">return<\/span> cache.get(context.name);\n    }\n\n    <span class=\"hljs-keyword\">const<\/span> value = func.call(<span class=\"hljs-keyword\">this<\/span>);\n\n    cache.set(context.name, value);\n\n    <span class=\"hljs-keyword\">return<\/span> value;\n  };\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Implementation would look the same:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Student<\/span> <\/span>{\n  @memoize\n  <span class=\"hljs-keyword\">get<\/span> gpa() {\n    <span class=\"hljs-comment\">\/\/ Expensive computation...<\/span>\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-number\">4.0<\/span>;\n  }\n}\n\n<span class=\"hljs-keyword\">const<\/span> milton = <span class=\"hljs-keyword\">new<\/span> Student();\n\nmilton.gpa <span class=\"hljs-comment\">\/\/ fresh<\/span>\nmilton.gpa <span class=\"hljs-comment\">\/\/ from the cache<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That context object contains some useful bits of information, by the way. One of those is the &#8220;kind&#8221; of field being decorated. That means we could even take this a step further by memoizing the getters&nbsp;<em>and<\/em>&nbsp;methods with the same decorator:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">memoize<\/span>(<span class=\"hljs-params\">func, context<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> cache = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Map<\/span>();\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> { kind, name } = context;\n\n    <span class=\"hljs-comment\">\/\/ Use different cache key based on \"kind.\"<\/span>\n    <span class=\"hljs-keyword\">const<\/span> cacheKey = kind === <span class=\"hljs-string\">'getter'<\/span> ? name : <span class=\"hljs-built_in\">JSON<\/span>.stringify(args);\n\n    <span class=\"hljs-keyword\">if<\/span> (cache.has(cacheKey)) {\n      <span class=\"hljs-keyword\">return<\/span> cache.get(cacheKey);\n    }\n\n    <span class=\"hljs-keyword\">const<\/span> value = func.call(<span class=\"hljs-keyword\">this<\/span>, ...args);\n\n    cache.set(cacheKey, value);\n\n    <span class=\"hljs-keyword\">return<\/span> value;\n  };\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You could take this much further, but we&#8217;ll draw the line there for now, and instead shift to something a little more complex.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"dependency-injection\">Dependency Injection<\/h2>\n\n\n\n<p>If you&#8217;ve worked with a framework like Laravel or Spring Boot, you&#8217;re familiar with dependency injection and the &#8220;inversion of control (IoC) container&#8221; for an application. It&#8217;s a useful feature, enabling you to write components more loosely coupled and easily testable. With native decorators, it&#8217;s possible to bring that core concept to vanilla JavaScript as well. No framework needed.<\/p>\n\n\n\n<p>Let&#8217;s say we&#8217;re building an application needing to send messages to various third-parties. Triggering an email, sending an analytics event, firing a push notification, etc. Each of these are abstracted into their own service classes:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">EmailService<\/span> <\/span>{\n  <span class=\"hljs-keyword\">constructor<\/span>() {\n    <span class=\"hljs-keyword\">this<\/span>.emailKey = process.env.EMAIL_KEY;\n  }\n}\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AnalyticsService<\/span> <\/span>{\n  <span class=\"hljs-keyword\">constructor<\/span>(analyticsKey) {\n    <span class=\"hljs-keyword\">this<\/span>.analyticsKey = analyticsKey;\n  }\n}\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">PushNotificationService<\/span> <\/span>{\n  <span class=\"hljs-keyword\">constructor<\/span>() {\n    <span class=\"hljs-keyword\">this<\/span>.pushNotificationKey = process.env.PUSH_NOTIFICATION_KEY;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Without decorators, it&#8217;s not difficult to instantiate those yourself. It might look something like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyApp<\/span> <\/span>{\n  <span class=\"hljs-keyword\">constructor<\/span>(\n    emailService = new EmailService(),\n    analyticsService = new AnalyticsService(),\n    pushNotificationService = new PushNotificationService()\n  ) {\n    <span class=\"hljs-keyword\">this<\/span>.emailService = emailService;\n    <span class=\"hljs-keyword\">this<\/span>.analyticsService = analyticsService;\n    <span class=\"hljs-keyword\">this<\/span>.pushNotificationService = pushNotificationService;\n\n    <span class=\"hljs-comment\">\/\/ Do stuff...<\/span>\n  }\n}\n\n<span class=\"hljs-keyword\">const<\/span> app = <span class=\"hljs-keyword\">new<\/span> MyApp();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But now you&#8217;ve cluttered your constructor with parameters that&#8217;ll never otherwise be used during runtime, and you\u2019re taking on full responsibility for instantiating those classes. There are workable solutions out there (like relying on separate modules to create singletons), but it&#8217;s not ergonomically great. And as complexity grows, this approach will become more cumbersome, especially as you attempt to maintain testability and stick to good inversion of control.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"dependency-injection-with-decorators\">Dependency Injection with Decorators<\/h2>\n\n\n\n<p>Now, let&#8217;s create a basic dependency injection mechanism with decorators. It\u2019ll be in charge of registering dependencies, instantiating them when necessary, and storing references to them in a centralized container.<\/p>\n\n\n\n<p>In a separate file (<code>container.js<\/code>), we&#8217;ll build a simple decorator used to register any classes we want to make available to the container.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> registry = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Map<\/span>();\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">register<\/span>(<span class=\"hljs-params\">args = &#91;]<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">clazz<\/span>) <\/span>{\n    registry.set(clazz, args);\n  };\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>There&#8217;s not much to it. We&#8217;re accepting the class itself and optional constructor arguments needed to spin it up. Next up, we&#8217;ll create a container to hold the instances we create, as well as an&nbsp;<code>inject()<\/code>&nbsp;decorator.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> container = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Map<\/span>();\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">inject<\/span>(<span class=\"hljs-params\">clazz<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">_value, context<\/span>) <\/span>{\n    context.addInitializer(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\"><\/span>) <\/span>{\n      <span class=\"hljs-keyword\">let<\/span> instance = container.get(clazz);\n\n      <span class=\"hljs-keyword\">if<\/span> (!instance) {\n        instance = <span class=\"hljs-built_in\">Reflect<\/span>.construct(clazz, registry.get(clazz));\n        container.set(clazz, instance);\n      }\n\n      <span class=\"hljs-keyword\">this<\/span>&#91;context.name] = instance;\n    });\n  };\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You&#8217;ll notice we&#8217;re using something else from the decorator specification. The <code>addInitializer()<\/code> method will fire a callback only after the decorated property has been defined. That means we\u2019ll be able to lazily instantiate our injected dependencies, rather than booting up every registered class all at once. It\u2019s a slight performance benefit. If a class uses the&nbsp;<code>EmailService<\/code>&nbsp;for example, but it&#8217;s never actually instantiated, we won\u2019t unnecessarily boot up an instance of&nbsp;<code>EmailService<\/code>&nbsp;either.<\/p>\n\n\n\n<p>That said, here&#8217;s what&#8217;s going on when the decorator is invoked:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We check for any active instance of the class in our container.<\/li>\n\n\n\n<li>If we don\u2019t have one, we create one using the arguments stored in the registry, and store it in the container.<\/li>\n\n\n\n<li>That instance is assigned to the name of the field we&#8217;ve decorated.<\/li>\n<\/ul>\n\n\n\n<p>Our application can now handle dependencies a little more elegantly.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { register, inject } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/container\"<\/span>;\n\n@register()\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">EmailService<\/span> <\/span>{\n  <span class=\"hljs-keyword\">constructor<\/span>() {\n    <span class=\"hljs-keyword\">this<\/span>.emailKey = process.env.EMAIL_KEY;\n  }\n}\n@register()\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AnalyticsService<\/span> <\/span>{\n  <span class=\"hljs-keyword\">constructor<\/span>(analyticsKey) {\n    <span class=\"hljs-keyword\">this<\/span>.analyticsKey = analyticsKey;\n  }\n}\n@register()\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">PushNotificationService<\/span> <\/span>{\n  <span class=\"hljs-keyword\">constructor<\/span>() {\n    <span class=\"hljs-keyword\">this<\/span>.pushNotificationKey = process.env.PUSH_NOTIFICATION_KEY;\n  }\n}\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyApp<\/span> <\/span>{\n  @inject(EmailService)\n  emailService;\n\n  @inject(AnalyticsService)\n  analyticsService;\n\n  @inject(PushNotificationService)\n  pushNotificationService;\n\n  <span class=\"hljs-keyword\">constructor<\/span>() {\n    <span class=\"hljs-comment\">\/\/ Do stuff.<\/span>\n  }\n}\n\n<span class=\"hljs-keyword\">const<\/span> app = <span class=\"hljs-keyword\">new<\/span> MyApp();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And as an added benefit, it&#8217;s straightforward to substitute those classes for mock versions of them as well. Rather than overriding class properties, we can less invasively inject our own mock classes into the container before the class we\u2019re testing is instantiated:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { vi, it } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'vitest'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { container } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/container'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { MyApp, EmailService } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'.\/main'<\/span>;\n\nit(<span class=\"hljs-string\">'does something'<\/span>, () =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> mockInstance = vi.fn();\n  container.set(EmailService, mockInstance);\n\n  <span class=\"hljs-keyword\">const<\/span> instance = <span class=\"hljs-keyword\">new<\/span> MyApp();\n  \n  <span class=\"hljs-comment\">\/\/ Test stuff.<\/span>\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That makes for less responsibility on us, tidy inversion of control, and straightforward testability. All made easy by a native feature.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"just-scratching-the-surface\">Just Scratching the Surface<\/h2>\n\n\n\n<p>If you read through\u00a0<a href=\"https:\/\/github.com\/tc39\/proposal-decorators?tab=readme-ov-file#adding-initialization-logic-with-addinitializer\">the proposal<\/a>, you&#8217;ll see that the decorator specification is far deeper than what&#8217;s been explored here, and will certainly open up some novel use cases in the future, especially once more runtimes support it. But you don&#8217;t need to master the depths of the feature in order to benefit. At its foundation, the decorator feature is still firmly seated on the decorator pattern. If you keep that in mind, you&#8217;ll be in a strong position to greatly benefit from it in your own code.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Native support for decorators is inevitable! It simplifies augmenting class methods, which can help with things like logging, memoization, debouncing, and dependency injection.<\/p>\n","protected":false},"author":25,"featured_media":3387,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[221,3],"class_list":["post-3381","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-decorators","tag-javascript"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/08\/cHJpdmF0ZS9sci9pbWFnZXMvd2Vic2l0ZS8yMDIyLTA3L2pvYjk1NS0wNDUtcC5wbmc.jpg?fit=1300%2C1300&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/3381","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=3381"}],"version-history":[{"count":11,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/3381\/revisions"}],"predecessor-version":[{"id":3404,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/3381\/revisions\/3404"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/3387"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=3381"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=3381"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=3381"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}