A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.md 36KB

4 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. title: Functional Mixins in ECMAScript 2015
  2. url: http://raganwald.com/2015/06/17/functional-mixins.html
  3. hash_url: 0970affe827a4197de22a7ccbe4fb897
  4. <p>In <a href="http://raganwald.com/2015/06/10/mixins.html">Prototypes are Objects</a>, we saw that you can emulate “mixins” using <code>Object.assign</code> on the prototypes that underly JavaScript “classes.” We’ll revisit this subject now and spend more time looking at mixing functionality into classes.</p>
  5. <p>First, a quick recap: In JavaScript, a “class” is implemented as a constructor function and its prototype, whether you write it directly, or use the <code>class</code> keyword. Instances of the class are created by calling the constructor with <code>new</code>. They “inherit” shared behaviour from the constructor’s <code>prototype</code> property.<sup id="fnref:delegate"><a href="#fn:delegate" class="footnote">1</a></sup></p>
  6. <h3 id="the-object-mixin-pattern">the object mixin pattern</h3>
  7. <p>One way to share behaviour scattered across multiple classes, or to untangle behaviour by factoring it out of an overweight prototype, is to extend a prototype with a <em>mixin</em>.</p>
  8. <p>Here’s a class of todo items:</p>
  9. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">class</span> <span class="nx">Todo</span> <span class="p">{</span>
  10. <span class="nx">constructor</span> <span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
  11. <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span> <span class="o">||</span> <span class="s1">'Untitled'</span><span class="p">;</span>
  12. <span class="k">this</span><span class="p">.</span><span class="nx">done</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
  13. <span class="p">}</span>
  14. <span class="k">do</span> <span class="p">()</span> <span class="p">{</span>
  15. <span class="k">this</span><span class="p">.</span><span class="nx">done</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
  16. <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
  17. <span class="p">}</span>
  18. <span class="nx">undo</span> <span class="p">()</span> <span class="p">{</span>
  19. <span class="k">this</span><span class="p">.</span><span class="nx">done</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
  20. <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
  21. <span class="p">}</span>
  22. <span class="p">}</span></code></pre></div>
  23. <p>And a “mixin” that is responsible for colour-coding:</p>
  24. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">Coloured</span> <span class="o">=</span> <span class="p">{</span>
  25. <span class="nx">setColourRGB</span> <span class="p">({</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">})</span> <span class="p">{</span>
  26. <span class="k">this</span><span class="p">.</span><span class="nx">colourCode</span> <span class="o">=</span> <span class="p">{</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">};</span>
  27. <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
  28. <span class="p">},</span>
  29. <span class="nx">getColourRGB</span> <span class="p">()</span> <span class="p">{</span>
  30. <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">colourCode</span><span class="p">;</span>
  31. <span class="p">}</span>
  32. <span class="p">};</span></code></pre></div>
  33. <p>Mixing colour coding into our Todo prototype is straightforward:</p>
  34. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="nx">Todo</span><span class="p">.</span><span class="nx">prototype</span><span class="p">,</span> <span class="nx">Coloured</span><span class="p">);</span>
  35. <span class="k">new</span> <span class="nx">Todo</span><span class="p">(</span><span class="s1">'test'</span><span class="p">)</span>
  36. <span class="p">.</span><span class="nx">setColourRGB</span><span class="p">({</span><span class="nx">r</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">g</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">b</span><span class="o">:</span> <span class="mi">3</span><span class="p">})</span>
  37. <span class="c1">//=&gt; {"name":"test","done":false,"colourCode":{"r":1,"g":2,"b":3}}</span></code></pre></div>
  38. <p>We can “upgrade” it to have a <a href="http://raganwald.com/2015/06/04/classes-are-expressions.html">private property</a> if we wish:</p>
  39. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">colourCode</span> <span class="o">=</span> <span class="nx">Symbol</span><span class="p">(</span><span class="s2">"colourCode"</span><span class="p">);</span>
  40. <span class="kr">const</span> <span class="nx">Coloured</span> <span class="o">=</span> <span class="p">{</span>
  41. <span class="nx">setColourRGB</span> <span class="p">({</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">})</span> <span class="p">{</span>
  42. <span class="k">this</span><span class="p">[</span><span class="nx">colourCode</span><span class="p">]</span><span class="o">=</span> <span class="p">{</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">};</span>
  43. <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
  44. <span class="p">},</span>
  45. <span class="nx">getColourRGB</span> <span class="p">()</span> <span class="p">{</span>
  46. <span class="k">return</span> <span class="k">this</span><span class="p">[</span><span class="nx">colourCode</span><span class="p">];</span>
  47. <span class="p">}</span>
  48. <span class="p">};</span></code></pre></div>
  49. <p>So far, very easy and very simple. This is a <em>pattern</em>, a recipe for solving a certain problem using a particular organization of code.</p>
  50. <p><a href="https://www.flickr.com/photos/chrisjrn/3771031871"><img src="/assets/images/macchiato.jpg" alt="Macchiato"/></a></p>
  51. <h3 id="functional-mixins">functional mixins</h3>
  52. <p>The object mixin we have above works properly, but our little recipe had two distinct steps: Define the mixin and then extend the class prototype. Angus Croll pointed out that it’s more elegant to define a mixin as a function rather than an object. He calls this a <a href="https://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/" title="A fresh look at JavaScript Mixins">functional mixin</a>. Here’s <code>Coloured</code> again, recast in functional form:</p>
  53. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">Coloured</span> <span class="o">=</span> <span class="p">(</span><span class="nx">target</span><span class="p">)</span> <span class="o">=&gt;</span>
  54. <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="p">{</span>
  55. <span class="nx">setColourRGB</span> <span class="p">({</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">})</span> <span class="p">{</span>
  56. <span class="k">this</span><span class="p">.</span><span class="nx">colourCode</span> <span class="o">=</span> <span class="p">{</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">};</span>
  57. <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
  58. <span class="p">},</span>
  59. <span class="nx">getColourRGB</span> <span class="p">()</span> <span class="p">{</span>
  60. <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">colourCode</span><span class="p">;</span>
  61. <span class="p">}</span>
  62. <span class="p">});</span>
  63. <span class="nx">Coloured</span><span class="p">(</span><span class="nx">Todo</span><span class="p">.</span><span class="nx">prototype</span><span class="p">);</span></code></pre></div>
  64. <p>We can make ourselves a <em>factory function</em> that also names the pattern:</p>
  65. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">FunctionalMixin</span> <span class="o">=</span> <span class="p">(</span><span class="nx">behaviour</span><span class="p">)</span> <span class="o">=&gt;</span>
  66. <span class="nx">target</span> <span class="o">=&gt;</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="nx">behaviour</span><span class="p">);</span></code></pre></div>
  67. <p>This allows us to define functional mixins neatly:</p>
  68. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">Coloured</span> <span class="o">=</span> <span class="nx">FunctionalMixin</span><span class="p">({</span>
  69. <span class="nx">setColourRGB</span> <span class="p">({</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">})</span> <span class="p">{</span>
  70. <span class="k">this</span><span class="p">.</span><span class="nx">colourCode</span> <span class="o">=</span> <span class="p">{</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">};</span>
  71. <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
  72. <span class="p">},</span>
  73. <span class="nx">getColourRGB</span> <span class="p">()</span> <span class="p">{</span>
  74. <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">colourCode</span><span class="p">;</span>
  75. <span class="p">}</span>
  76. <span class="p">});</span></code></pre></div>
  77. <h3 id="enumerability">enumerability</h3>
  78. <p>If we look at the way <code>class</code> defines prototypes, we find that the methods defined are not enumerable by default. This works around a common error where programmers iterate over the keys of an instance and fail to test for <code>.hasOwnProperty</code>.</p>
  79. <p>Our object mixin pattern does not work this way, the methods defined in a mixin <em>are</em> enumerable by default, and if we carefully defined them to be non-enumerable, <code>Object.assign</code> wouldn’t mix them into the target prototype, because <code>Object.assign</code> only assigns enumerable properties.</p>
  80. <p>And thus:</p>
  81. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">Coloured</span><span class="p">(</span><span class="nx">Todo</span><span class="p">.</span><span class="nx">prototype</span><span class="p">)</span>
  82. <span class="kr">const</span> <span class="nx">urgent</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Todo</span><span class="p">(</span><span class="s2">"finish blog post"</span><span class="p">);</span>
  83. <span class="nx">urgent</span><span class="p">.</span><span class="nx">setColourRGB</span><span class="p">({</span><span class="nx">r</span><span class="o">:</span> <span class="mi">256</span><span class="p">,</span> <span class="nx">g</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">b</span><span class="o">:</span> <span class="mi">0</span><span class="p">});</span>
  84. <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">property</span> <span class="k">in</span> <span class="nx">urgent</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">property</span><span class="p">);</span>
  85. <span class="c1">// =&gt;</span>
  86. <span class="nx">name</span>
  87. <span class="nx">done</span>
  88. <span class="nx">colourCode</span>
  89. <span class="nx">setColourRGB</span>
  90. <span class="nx">getColourRGB</span></code></pre></div>
  91. <p>As we can see, the <code>setColourRGB</code> and <code>getColourRGB</code> methods are enumerated, although the <code>do</code> and <code>undo</code> methods are not. This can be a problem with naïve code: we can’t always rewrite all the <em>other</em> code to carefully use <code>.hasOwnProperty</code>.</p>
  92. <p>One benefit of functional mixins is that we can solve this problem and transparently make mixins behave like <code>class</code>:</p>
  93. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">FunctionalMixin</span> <span class="o">=</span> <span class="p">(</span><span class="nx">behaviour</span><span class="p">)</span> <span class="o">=&gt;</span>
  94. <span class="kd">function</span> <span class="p">(</span><span class="nx">target</span><span class="p">)</span> <span class="p">{</span>
  95. <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">property</span> <span class="nx">of</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertyNames</span><span class="p">(</span><span class="nx">behaviour</span><span class="p">))</span>
  96. <span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="nx">property</span><span class="p">,</span> <span class="p">{</span> <span class="nx">value</span><span class="o">:</span> <span class="nx">behaviour</span><span class="p">[</span><span class="nx">property</span><span class="p">]</span> <span class="p">})</span>
  97. <span class="k">return</span> <span class="nx">target</span><span class="p">;</span>
  98. <span class="p">}</span></code></pre></div>
  99. <p>The above code supports methods with ordinary string names, but sometimes methods are declared with symbols (typically to create private methods). Although we won’t discuss that pattern yet, we can support those too:</p>
  100. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">FunctionalMixin</span> <span class="o">=</span> <span class="p">(</span><span class="nx">behaviour</span><span class="p">)</span> <span class="o">=&gt;</span>
  101. <span class="kd">function</span> <span class="p">(</span><span class="nx">target</span><span class="p">)</span> <span class="p">{</span>
  102. <span class="kr">const</span> <span class="nx">instanceKeys</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertyNames</span><span class="p">(</span><span class="nx">behaviour</span><span class="p">)</span>
  103. <span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertySymbols</span><span class="p">(</span><span class="nx">behaviour</span><span class="p">));</span>
  104. <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">property</span> <span class="nx">of</span> <span class="nx">instanceKeys</span><span class="p">)</span>
  105. <span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="nx">property</span><span class="p">,</span> <span class="p">{</span> <span class="nx">value</span><span class="o">:</span> <span class="nx">behaviour</span><span class="p">[</span><span class="nx">property</span><span class="p">]</span> <span class="p">})</span>
  106. <span class="k">return</span> <span class="nx">target</span><span class="p">;</span>
  107. <span class="p">}</span></code></pre></div>
  108. <p>Writing this out as a pattern would be tedious and error-prone. Encapsulating the behaviour into a function is a small win.</p>
  109. <p><a href="https://www.flickr.com/photos/yellowskyphotography/7449919584"><img src="/assets/images/jbts.jpg" alt="Just Below the Surface"/></a></p>
  110. <h3 id="mixin-responsibilities">mixin responsibilities</h3>
  111. <p>Like classes, mixins are metaobjects: They define behaviour for instances. In addition to defining behaviour in the form of methods, classes are also responsible for initializing instances. But sometimes, classes and metaobjects handle additional responsibilities.</p>
  112. <p>For example, sometimes a particular concept is associated with some well-known constants. When using a class, can be handy to namespace such values in the class itself:</p>
  113. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">class</span> <span class="nx">Todo</span> <span class="p">{</span>
  114. <span class="nx">constructor</span> <span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
  115. <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span> <span class="o">||</span> <span class="nx">Todo</span><span class="p">.</span><span class="nx">DEFAULT_NAME</span><span class="p">;</span>
  116. <span class="k">this</span><span class="p">.</span><span class="nx">done</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
  117. <span class="p">}</span>
  118. <span class="k">do</span> <span class="p">()</span> <span class="p">{</span>
  119. <span class="k">this</span><span class="p">.</span><span class="nx">done</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
  120. <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
  121. <span class="p">}</span>
  122. <span class="nx">undo</span> <span class="p">()</span> <span class="p">{</span>
  123. <span class="k">this</span><span class="p">.</span><span class="nx">done</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
  124. <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
  125. <span class="p">}</span>
  126. <span class="p">}</span>
  127. <span class="nx">Todo</span><span class="p">.</span><span class="nx">DEFAULT_NAME</span> <span class="o">=</span> <span class="s1">'Untitled'</span><span class="p">;</span>
  128. <span class="c1">// If we are sticklers for read-only constants, we could write:</span>
  129. <span class="c1">// Object.defineProperty(Todo, 'DEFAULT_NAME', {value: 'Untitled'});</span></code></pre></div>
  130. <p>We can’t really do the same thing with simple mixins, because all of the properties in a simple mixin end up being mixed into the prototype of instances we create by default. For example, let’s say we want to define <code>Coloured.RED</code>, <code>Coloured.GREEN</code>, and <code>Coloured.BLUE</code>. But we don’t want any specific coloured instance to define <code>RED</code>, <code>GREEN</code>, or <code>BLUE</code>.</p>
  131. <p>Again, we can solve this problem by building a functional mixin. Our <code>FunctionalMixin</code> factory function will accept an optional dictionary of read-only mixin properties, provided they are associated with a special key:</p>
  132. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">shared</span> <span class="o">=</span> <span class="nx">Symbol</span><span class="p">(</span><span class="s2">"shared"</span><span class="p">);</span>
  133. <span class="kd">function</span> <span class="nx">FunctionalMixin</span> <span class="p">(</span><span class="nx">behaviour</span><span class="p">)</span> <span class="p">{</span>
  134. <span class="kr">const</span> <span class="nx">instanceKeys</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertyNames</span><span class="p">(</span><span class="nx">behaviour</span><span class="p">)</span>
  135. <span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertySymbols</span><span class="p">(</span><span class="nx">behaviour</span><span class="p">))</span>
  136. <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">key</span> <span class="o">=&gt;</span> <span class="nx">key</span> <span class="o">!==</span> <span class="nx">shared</span><span class="p">);</span>
  137. <span class="kr">const</span> <span class="nx">sharedBehaviour</span> <span class="o">=</span> <span class="nx">behaviour</span><span class="p">[</span><span class="nx">shared</span><span class="p">]</span> <span class="o">||</span> <span class="p">{};</span>
  138. <span class="kr">const</span> <span class="nx">sharedKeys</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertyNames</span><span class="p">(</span><span class="nx">sharedBehaviour</span><span class="p">)</span>
  139. <span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertySymbols</span><span class="p">(</span><span class="nx">sharedBehaviour</span><span class="p">));</span>
  140. <span class="kd">function</span> <span class="nx">mixin</span> <span class="p">(</span><span class="nx">target</span><span class="p">)</span> <span class="p">{</span>
  141. <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">property</span> <span class="nx">of</span> <span class="nx">instanceKeys</span><span class="p">)</span>
  142. <span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="nx">property</span><span class="p">,</span> <span class="p">{</span> <span class="nx">value</span><span class="o">:</span> <span class="nx">behaviour</span><span class="p">[</span><span class="nx">property</span><span class="p">]</span> <span class="p">});</span>
  143. <span class="k">return</span> <span class="nx">target</span><span class="p">;</span>
  144. <span class="p">}</span>
  145. <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">property</span> <span class="nx">of</span> <span class="nx">sharedKeys</span><span class="p">)</span>
  146. <span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">mixin</span><span class="p">,</span> <span class="nx">property</span><span class="p">,</span> <span class="p">{</span>
  147. <span class="nx">value</span><span class="o">:</span> <span class="nx">sharedBehaviour</span><span class="p">[</span><span class="nx">property</span><span class="p">],</span>
  148. <span class="nx">enumerable</span><span class="o">:</span> <span class="nx">sharedBehaviour</span><span class="p">.</span><span class="nx">propertyIsEnumerable</span><span class="p">(</span><span class="nx">property</span><span class="p">)</span>
  149. <span class="p">});</span>
  150. <span class="k">return</span> <span class="nx">mixin</span><span class="p">;</span>
  151. <span class="p">}</span>
  152. <span class="nx">FunctionalMixin</span><span class="p">.</span><span class="nx">shared</span> <span class="o">=</span> <span class="nx">shared</span><span class="p">;</span></code></pre></div>
  153. <p>And now we can write:</p>
  154. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">Coloured</span> <span class="o">=</span> <span class="nx">FunctionalMixin</span><span class="p">({</span>
  155. <span class="nx">setColourRGB</span> <span class="p">({</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">})</span> <span class="p">{</span>
  156. <span class="k">this</span><span class="p">.</span><span class="nx">colourCode</span> <span class="o">=</span> <span class="p">{</span><span class="nx">r</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">};</span>
  157. <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
  158. <span class="p">},</span>
  159. <span class="nx">getColourRGB</span> <span class="p">()</span> <span class="p">{</span>
  160. <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">colourCode</span><span class="p">;</span>
  161. <span class="p">},</span>
  162. <span class="p">[</span><span class="nx">FunctionalMixin</span><span class="p">.</span><span class="nx">shared</span><span class="p">]</span><span class="o">:</span> <span class="p">{</span>
  163. <span class="nx">RED</span><span class="o">:</span> <span class="p">{</span> <span class="nx">r</span><span class="o">:</span> <span class="mi">255</span><span class="p">,</span> <span class="nx">g</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">b</span><span class="o">:</span> <span class="mi">0</span> <span class="p">},</span>
  164. <span class="nx">GREEN</span><span class="o">:</span> <span class="p">{</span> <span class="nx">r</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">g</span><span class="o">:</span> <span class="mi">255</span><span class="p">,</span> <span class="nx">b</span><span class="o">:</span> <span class="mi">0</span> <span class="p">},</span>
  165. <span class="nx">BLUE</span><span class="o">:</span> <span class="p">{</span> <span class="nx">r</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">g</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">b</span><span class="o">:</span> <span class="mi">255</span> <span class="p">},</span>
  166. <span class="p">}</span>
  167. <span class="p">});</span>
  168. <span class="nx">Coloured</span><span class="p">(</span><span class="nx">Todo</span><span class="p">.</span><span class="nx">prototype</span><span class="p">)</span>
  169. <span class="kr">const</span> <span class="nx">urgent</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Todo</span><span class="p">(</span><span class="s2">"finish blog post"</span><span class="p">);</span>
  170. <span class="nx">urgent</span><span class="p">.</span><span class="nx">setColourRGB</span><span class="p">(</span><span class="nx">Coloured</span><span class="p">.</span><span class="nx">RED</span><span class="p">);</span>
  171. <span class="nx">urgent</span><span class="p">.</span><span class="nx">getColourRGB</span><span class="p">()</span>
  172. <span class="c1">//=&gt; {"r":255,"g":0,"b":0}</span></code></pre></div>
  173. <h3 id="mixin-methods">mixin methods</h3>
  174. <p>Such properties need not be values. Sometimes, classes have methods. And likewise, sometimes it makes sense for a mixin to have its own methods. One example concerns <code>instanceof</code>.</p>
  175. <p>In earlier versions of ECMAScript, <code>instanceof</code> is an operator that checks to see whether the prototype of an instance matches the prototype of a constructor function. It works just fine with “classes,” but it does not work “out of the box” with mixins:</p>
  176. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">urgent</span> <span class="k">instanceof</span> <span class="nx">Todo</span>
  177. <span class="c1">//=&gt; true</span>
  178. <span class="nx">urgent</span> <span class="k">instanceof</span> <span class="nx">Coloured</span>
  179. <span class="c1">//=&gt; false</span></code></pre></div>
  180. <p>To handle this and some other issues where programmers are creating their own notion of dynamic types, or managing prototypes directly with <code>Object.create</code> and <code>Object.setPrototypeOf</code>, ECMAScript 2015 provides a way to override the built-in <code>instanceof</code> behaviour: An object can define a method associated with a well-known symbol, <code>Symbol.instanceOf</code>.</p>
  181. <p>We can test this quickly:<sup id="fnref:but"><a href="#fn:but" class="footnote">2</a></sup></p>
  182. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">Coloured</span><span class="p">[</span><span class="nx">Symbol</span><span class="p">.</span><span class="nx">instanceOf</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nx">instance</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kc">true</span>
  183. <span class="nx">urgent</span> <span class="k">instanceof</span> <span class="nx">Coloured</span>
  184. <span class="c1">//=&gt; true</span>
  185. <span class="p">{}</span> <span class="k">instanceof</span> <span class="nx">Coloured</span>
  186. <span class="c1">//=&gt; true</span></code></pre></div>
  187. <p>Of course, that is not semantically correct. But using this technique, we can write:</p>
  188. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kr">const</span> <span class="nx">shared</span> <span class="o">=</span> <span class="nx">Symbol</span><span class="p">(</span><span class="s2">"shared"</span><span class="p">);</span>
  189. <span class="kd">function</span> <span class="nx">FunctionalMixin</span> <span class="p">(</span><span class="nx">behaviour</span><span class="p">)</span> <span class="p">{</span>
  190. <span class="kr">const</span> <span class="nx">instanceKeys</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertyNames</span><span class="p">(</span><span class="nx">behaviour</span><span class="p">)</span>
  191. <span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertySymbols</span><span class="p">(</span><span class="nx">behaviour</span><span class="p">))</span>
  192. <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">key</span> <span class="o">=&gt;</span> <span class="nx">key</span> <span class="o">!==</span> <span class="nx">shared</span><span class="p">);</span>
  193. <span class="kr">const</span> <span class="nx">sharedBehaviour</span> <span class="o">=</span> <span class="nx">behaviour</span><span class="p">[</span><span class="nx">shared</span><span class="p">]</span> <span class="o">||</span> <span class="p">{};</span>
  194. <span class="kr">const</span> <span class="nx">sharedKeys</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertyNames</span><span class="p">(</span><span class="nx">sharedBehaviour</span><span class="p">)</span>
  195. <span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">getOwnPropertySymbols</span><span class="p">(</span><span class="nx">sharedBehaviour</span><span class="p">));</span>
  196. <span class="kr">const</span> <span class="nx">typeTag</span> <span class="o">=</span> <span class="nx">Symbol</span><span class="p">(</span><span class="s2">"isA"</span><span class="p">);</span>
  197. <span class="kd">function</span> <span class="nx">mixin</span> <span class="p">(</span><span class="nx">target</span><span class="p">)</span> <span class="p">{</span>
  198. <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">property</span> <span class="nx">of</span> <span class="nx">instanceKeys</span><span class="p">)</span>
  199. <span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="nx">property</span><span class="p">,</span> <span class="p">{</span> <span class="nx">value</span><span class="o">:</span> <span class="nx">behaviour</span><span class="p">[</span><span class="nx">property</span><span class="p">]</span> <span class="p">});</span>
  200. <span class="nx">target</span><span class="p">[</span><span class="nx">typeTag</span><span class="p">]</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
  201. <span class="k">return</span> <span class="nx">target</span><span class="p">;</span>
  202. <span class="p">}</span>
  203. <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">property</span> <span class="nx">of</span> <span class="nx">sharedKeys</span><span class="p">)</span>
  204. <span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">mixin</span><span class="p">,</span> <span class="nx">property</span><span class="p">,</span> <span class="p">{</span>
  205. <span class="nx">value</span><span class="o">:</span> <span class="nx">sharedBehaviour</span><span class="p">[</span><span class="nx">property</span><span class="p">],</span>
  206. <span class="nx">enumerable</span><span class="o">:</span> <span class="nx">sharedBehaviour</span><span class="p">.</span><span class="nx">propertyIsEnumerable</span><span class="p">(</span><span class="nx">property</span><span class="p">)</span>
  207. <span class="p">});</span>
  208. <span class="nx">mixin</span><span class="p">[</span><span class="nx">Symbol</span><span class="p">.</span><span class="nx">instanceOf</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nx">instance</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="o">!!</span><span class="nx">instance</span><span class="p">[</span><span class="nx">typeTag</span><span class="p">];</span>
  209. <span class="k">return</span> <span class="nx">mixin</span><span class="p">;</span>
  210. <span class="p">}</span>
  211. <span class="nx">FunctionalMixin</span><span class="p">.</span><span class="nx">shared</span> <span class="o">=</span> <span class="nx">shared</span><span class="p">;</span>
  212. <span class="nx">urgent</span> <span class="k">instanceof</span> <span class="nx">Coloured</span>
  213. <span class="c1">//=&gt; true</span>
  214. <span class="p">{}</span> <span class="k">instanceof</span> <span class="nx">Coloured</span>
  215. <span class="c1">//=&gt; false</span></code></pre></div>
  216. <p>Do you need to implement <code>instanceof</code>? Quite possibly not. “Rolling your own polymorphism” is usually a last resort. But it can be handy for writing test cases, and a few daring framework developers might be working on multiple dispatch and pattern-matching for functions.</p>
  217. <h3 id="summary">summary</h3>
  218. <p>The charm of the object mixin pattern is its simplicity: It really does not need an abstraction wrapped around an object literal and <code>Object.assign</code>.</p>
  219. <p>However, behaviour defined with the mixin pattern is <em>slightly</em> different than behaviour defined with the <code>class</code> keyword. Two examples of these differences are enumerability and mixin properties (such as constants and mixin methods like <code>[Symbol.instanceof]</code>).</p>
  220. <p>Functional mixins provide an opportunity to implement such functionality, at the cost of some complexity in the <code>FunctionalMixin</code> function that creates functional mixins.</p>
  221. <p>As a general rule, it’s best to have things behave as similarly as possible in the domain code, and this sometimes does involve some extra complexity in the infrastructure code. But that is more of a guideline than a hard-and-fast rule, and for this reason there is a place for both the object mixin pattern <em>and</em> functional mixins in JavaScript.</p>