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.

пре 5 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. title: Real-World JavaScript Anti-Patterns (Part One)
  2. url: http://blog.javascripting.com/2014/11/06/real-world-javascript-anti-patterns/
  3. hash_url: e670da5770c6ca884d4915552766f8d0
  4. <p>Code reviews are a perfect opportunity to continue learning and improving your coding style. I work with a team of experienced developers, so I don’t see many really glaring errors. But when I am called on to review third-party code, I’ve noticed certain bad practices crop up over and over again. Some of these may seem obvious, but hopefully there will be a useful tip or two here for JavaScript developers of every skill level. All examples are based on real production code I’ve reviewed, although (variable) names have been changed to protect the innocent.</p>
  5. <p>Consider this gem: </p>
  6. <pre><code class="language-javascript">var otherElements = element.classList.contains('my-class') ?
  7. element.parentNode.parentNode.parentNode.childNodes :
  8. element.parentNode.childNodes;
  9. </code></pre>
  10. <p>The project in question already included jQuery, so one obvious improvement would be to use selectors instead of the execrable DOM API. But that doesn’t address the fundamental issue with this code: it will break if the slightest change is made to the associated markup. The best solution would be to rethink the approach entirely, perhaps using HTML classes to assign semantics to specific elements instead of chaining DOM calls.</p>
  11. <p>Let’s look at some more subtle examples. Even small changes can improve code readability and robustness. This is especially significant when new developers join a project that is already underway. They will get up to speed more quickly and make less mistakes if they don’t need to decipher the existing code base. Clearer code will also prevent them from trying to find hidden meaning where there is none. Consider this event handler:</p>
  12. <pre><code class="language-javascript">MyPage.prototype.resize = function() {
  13. this.renderer.view.style.width = window.innerWidth + 'px';
  14. this.renderer.view.style.height =
  15. window.innerWidth * 0.48828125 + 'px';
  16. };
  17. </code></pre>
  18. <p>Do we need to be so precise? Probably the developer just copied and pasted a constant from their calculator. The less significant digits are completely useless and serve only to obfuscate the intent of the code. Just writing <code>0.49</code> would be an improvement but still doesn’t make it clear what is going on. It would be much better to assign the number to a constant while making it clear how it was derived: </p>
  19. <pre><code class="language-javascript">const SCALING_FACTOR = 500/1024;
  20. ...
  21. MyPage.prototype.resize() = function() {
  22. ...
  23. this.renderer.view.style.height =
  24. window.innerWidth * SCALING_FACTOR + 'px';
  25. }
  26. </code></pre>
  27. <p>Another example is an event handler attached to a button: </p>
  28. <pre><code class="language-javascript">MyButton.prototype.new = function(e) {
  29. var myTarget = e.target;
  30. ...
  31. };
  32. </code></pre>
  33. <p>That’s right, a function declared on a prototype can be named using a reserved word. Surprisingly this works, but to say it’s confusing would be an understatement. Clear and careful naming is as important as code structure itself.</p>
  34. <p>Many anti-patterns relate to basic collections and operations. A classic example: </p>
  35. <pre><code class="language-javascript">items.forEach(function(item) {
  36. doSomething(item);
  37. });
  38. </code></pre>
  39. <p>Similar constructs can be seen for promises, click handlers and anywhere where you unthinkingly type the <code>function</code> keyword. Keep it simple: </p>
  40. <pre><code class="language-javascript">items.forEach(doSomething);
  41. </code></pre>
  42. <p>The situation is more complicated if you need to invoke an object’s method with the right context. Code like this is very common: </p>
  43. <pre><code class="language-javascript">var _this = this;
  44. items.forEach(function(item) {
  45. _this.doSomething(item);
  46. });
  47. </code></pre>
  48. <p>Developers tend to forget about the <code>thisArg</code> parameter of <code>forEach</code> and similar functions, which leads to cleaner, clearer code: </p>
  49. <pre><code class="language-javascript">items.forEach(function(item) {
  50. this.doSomething(item);
  51. }, this);
  52. </code></pre>
  53. <p>And once again, if we are calling a single function, we can change it to a one-liner: </p>
  54. <pre><code class="language-javascript">items.forEach(this.doSomething, this);
  55. </code></pre>
  56. <p>But what about functions that don’t take a <code>thisArg</code> parameter? Imagine you want to avoid the clumsy <code>_this</code> variable when access an object property inside a jQuery click handler. Assigning to <code>_this</code> is unnecessary, thanks to the underused <code>Function.prototype.bind()</code> method: </p>
  57. <pre><code class="language-javascript">el.click(function(e) {
  58. this.clickCount++;
  59. }.bind(this));
  60. </code></pre>
  61. <p>This isn’t the only practical use of the <code>bind</code> function. Less used but no less useful is the ability to create partials with fixed argument values. Remember the dreaded lint error “Don’t make functions within a loop”? Imagine you have an array of DOM elements: </p>
  62. <pre><code class="language-javascript">var i, elems = document.getElementsByClassName("myClass");
  63. </code></pre>
  64. <p>…and the following (wrong) code:</p>
  65. <pre><code class="language-javascript">for (i = 0; i &lt; elems.length; i++) {
  66. elems[i].addEventListener("click", function() {
  67. this.innerHTML = i;
  68. });
  69. }
  70. </code></pre>
  71. <p>The traditional solution is to define a factory function to create the click handler. </p>
  72. <pre><code class="language-javascript">function makeClickHandler(i) {
  73. return function() {
  74. this.innerHTML = i;
  75. };
  76. }
  77. for (i = 0; i &lt; elems.length; i++) {
  78. elems[i].addEventListener("click", makeClickHandler(i));
  79. }
  80. </code></pre>
  81. <p>But <code>Function.prototype.bind()</code> is essentially a factory built into the language itself: </p>
  82. <pre><code class="language-javascript">function handleClick(i) {
  83. this.innerHTML = i;
  84. }
  85. for (i = 0; i &lt; elems.length; i++) {
  86. elems[i].addEventListener("click", handleClick.bind(this, i));
  87. }
  88. </code></pre>
  89. <p>Back to <code>forEach</code>. It is such common construct that is sometimes over- (and mis-)used: </p>
  90. <pre><code class="language-javascript">var left = 0, right = 1;
  91. [left, right].forEach(function(i) {
  92. ...
  93. }, this);
  94. </code></pre>
  95. <p>Here an old-fashioned <code>for</code> loop would be a better choice. You might protest that the author’s choice of variables names is intended to make the code’s semantics more apparent. In this case you can use a code comment or choose a more appropriate data representation. Which representation depends on the specific code, but something like the following is clearly superior to the previous example (note the use of the more powerful Lo-Dash/Underscore <code>forEach</code>): </p>
  96. <pre><code class="language-javascript">data = {left: ..., right: ...};
  97. _.forEach(data, function(item, key) {
  98. ...
  99. });
  100. </code></pre>
  101. <p>Another classic example of <code>forEach</code> abuse: </p>
  102. <pre><code class="language-javascript">var items = [];
  103. data.forEach(function(dataItem) {
  104. var item = dataItem * 4;
  105. items.push(item);
  106. });
  107. </code></pre>
  108. <p>In this simple example it is easy to see that <code>map</code> would be a better choice: </p>
  109. <pre><code class="language-javascript">var items = data.map(function(dataItem) {
  110. return dataItem * 4;
  111. });
  112. </code></pre>
  113. <p>In more complex cases the correct array method can less obvious: </p>
  114. <pre><code class="language-javascript">var lowCounter = 0;
  115. var items = data.map(function(dataItem) {
  116. if (dataItem &lt; 10) lowCounter++;
  117. return dataItem * 5;
  118. });
  119. </code></pre>
  120. <p>Is it appropriate to use <code>map</code> here, considering that the loop has an unrelated side effect? Unless the array is large enough for performance to be a consideration, it would probably be better to use two separate calls, one for the <code>map</code> and one for the side effect: </p>
  121. <pre><code class="language-javascript">var items = data.map(function(dataItem) { return dataItem*5; });
  122. var lowCounter = data.reduce(function(total, current) {
  123. return total + (current &lt; 10);
  124. }, 0);
  125. </code></pre>
  126. <p>One final example: imagine you are using Lo-Dash/Underscore and want merge an array of objects into a single new object. </p>
  127. <pre><code class="language-javascript">var confs = [{a: 1, b: 14}, {foo: 6}, ... ]
  128. </code></pre>
  129. <p>A naive approach might use <code>forEach</code> and <code>_.merge</code>: </p>
  130. <pre><code class="language-javascript">var result = {};
  131. confs.forEach(function(item) {
  132. _.merge(result, item)
  133. });
  134. </code></pre>
  135. <p>A shorter functional approach: </p>
  136. <pre><code class="language-javascript">var result = {} confs.forEach(_.partial(_.merge, result));
  137. </code></pre>
  138. <p>A similar approach using <code>apply</code> (don’t try this with a long array though since each item will be passed to <code>merge</code> as a parameter) </p>
  139. <pre><code class="language-javascript">var result = _.partial(_.merge, {}).apply(this, confs);
  140. </code></pre>
  141. <p>Best of all, of course, is <code>reduce</code>: </p>
  142. <pre><code class="language-javascript">var result = confs.reduce(_.merge, {});
  143. </code></pre>
  144. <p>Use of <code>_.partial</code> makes the code shorter but arguably harder to read. Less code isn’t always better when the result is less understandable. Fortunately we don’t need to weigh the trade-offs in this case since the final variant using <code>reduce</code> is clearly superior.</p>