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 90KB

5 lat temu
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  1. title: 10 bonnes pratiques JavaScript
  2. url: http://www.js-attitude.fr/2013/01/21/dix-bonnes-pratiques-javascript/
  3. hash_url: 39c6b8b0ae16fbd5b07385deb48dba10
  4. <p>Au fil des formations, je remarque que de nombreuses bonnes pratiques que je signale en passant dans le code, par acquis de conscience, ne sont pas connues, ou pas comprises, ou juste surprenantes pour les stagiaires. C’est l’occasion de me souvenir que dans tous les domaines, ce qui peut paraître évident et « connu de tous » ne l’est pas forcément, et qu’il est toujours bon de remettre en lumière des savoirs dont on imagine, souvent à tort, qu’ils sont monnaie courante.</p>
  5. <p>Voici donc dix bonnes pratiques choisies parmi un large ensemble de candidates ; vous en connaîtrez sûrement certaines, mais probablement pas toutes. Je pensais qu’il en faudrait 10 pour faire un article un peu consistant, et au final c’est un mammouth qui aurait pu être découpé en plusieurs articles histoire de faire durer le plaisir… Mais je suis d’humeur généreuse et j’ai encore plein d’articles sous le coude, alors je vous livre tout ça d’un coup :-)</p>
  6. <p>N’hésitez pas à faire vos retours en commentaires !</p>
  7. <h2>1. Court-circuiter les blocs plutôt que les imbriquer</h2>
  8. <p>Court-circuiter le code, en JavaScript, c’est recourir à <code>return</code>, <code>break</code> ou <code>continue</code> pour sauter tout ou partie du contexte en cours (fonction ou boucle), et s’épargner ainsi des blocs imbriqués inutiles, avec ce que ça suppose d’indentation supplémentaire…</p>
  9. <p>Il est certes primordial de bien indenter son code, quel que soit le langage. Dès qu’on ne se limite pas à un code trivial de 2-3 lignes, visualiser les dépendances logiques des conditions, boucles et fonctions devient impératif.</p>
  10. <p>Toutefois, il est dommage d’indenter inutilement. Trop d’indentations imbriquées crée souvent un <em>a priori</em> négatif sur le code en donnant une vision faussée de sa complexité. Cela rend aussi le code moins lisible et facile à appréhender.</p>
  11. <p>Voyons quelques exemples.</p>
  12. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  13. <span class="line-number">2</span>
  14. <span class="line-number">3</span>
  15. <span class="line-number">4</span>
  16. <span class="line-number">5</span>
  17. <span class="line-number">6</span>
  18. <span class="line-number">7</span>
  19. <span class="line-number">8</span>
  20. <span class="line-number">9</span>
  21. <span class="line-number">10</span>
  22. <span class="line-number">11</span>
  23. <span class="line-number">12</span>
  24. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">function</span> <span class="nx">processItems</span><span class="p">(</span><span class="nx">list</span><span class="p">)</span> <span class="p">{</span>
  25. </span><span class="line"> <span class="kd">var</span> <span class="nx">result</span> <span class="o">=</span> <span class="p">[];</span>
  26. </span><span class="line">
  27. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
  28. </span><span class="line"> <span class="nx">normalize</span><span class="p">(</span><span class="nx">items</span><span class="p">);</span>
  29. </span><span class="line"> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">items</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
  30. </span><span class="line"> <span class="c1">// code bien long…</span>
  31. </span><span class="line"> <span class="p">}</span>
  32. </span><span class="line"> <span class="p">}</span>
  33. </span><span class="line">
  34. </span><span class="line"> <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
  35. </span><span class="line"><span class="p">}</span>
  36. </span></code></pre></td></tr></table></div></figure>
  37. <p>Ici il est dommage d’avoir deux niveaux. Le cœur de code étant dans un <code>if</code>, il suffit de tester la condition inverse et de court-circuiter avec un <code>return</code> :</p>
  38. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  39. <span class="line-number">2</span>
  40. <span class="line-number">3</span>
  41. <span class="line-number">4</span>
  42. <span class="line-number">5</span>
  43. <span class="line-number">6</span>
  44. <span class="line-number">7</span>
  45. <span class="line-number">8</span>
  46. <span class="line-number">9</span>
  47. <span class="line-number">10</span>
  48. <span class="line-number">11</span>
  49. <span class="line-number">12</span>
  50. <span class="line-number">13</span>
  51. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">function</span> <span class="nx">processItems</span><span class="p">(</span><span class="nx">list</span><span class="p">)</span> <span class="p">{</span>
  52. </span><span class="line"> <span class="kd">var</span> <span class="nx">result</span> <span class="o">=</span> <span class="p">[];</span>
  53. </span><span class="line">
  54. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span>
  55. </span><span class="line"> <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
  56. </span><span class="line">
  57. </span><span class="line"> <span class="nx">normalize</span><span class="p">(</span><span class="nx">items</span><span class="p">);</span>
  58. </span><span class="line"> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">items</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
  59. </span><span class="line"> <span class="c1">// code bien long…</span>
  60. </span><span class="line"> <span class="p">}</span>
  61. </span><span class="line">
  62. </span><span class="line"> <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
  63. </span><span class="line"><span class="p">}</span>
  64. </span></code></pre></td></tr></table></div></figure>
  65. <p>Le même principe peut s’appliquer dans les boucles, en utilisant suivant les cas <code>continue</code> ou <code>break</code>. Ainsi, le code suivant :</p>
  66. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  67. <span class="line-number">2</span>
  68. <span class="line-number">3</span>
  69. <span class="line-number">4</span>
  70. <span class="line-number">5</span>
  71. <span class="line-number">6</span>
  72. <span class="line-number">7</span>
  73. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="k">while</span> <span class="p">(</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
  74. </span><span class="line"> <span class="kd">var</span> <span class="nx">item</span> <span class="o">=</span> <span class="nx">items</span><span class="p">.</span><span class="nx">pop</span><span class="p">();</span>
  75. </span><span class="line">
  76. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="s1">'taggable'</span><span class="p">))</span> <span class="p">{</span>
  77. </span><span class="line"> <span class="c1">// code bien long…</span>
  78. </span><span class="line"> <span class="p">}</span>
  79. </span><span class="line"><span class="p">}</span>
  80. </span></code></pre></td></tr></table></div></figure>
  81. <p>…deviendrait celui-ci :</p>
  82. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  83. <span class="line-number">2</span>
  84. <span class="line-number">3</span>
  85. <span class="line-number">4</span>
  86. <span class="line-number">5</span>
  87. <span class="line-number">6</span>
  88. <span class="line-number">7</span>
  89. <span class="line-number">8</span>
  90. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="k">while</span> <span class="p">(</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
  91. </span><span class="line"> <span class="kd">var</span> <span class="nx">item</span> <span class="o">=</span> <span class="nx">items</span><span class="p">.</span><span class="nx">pop</span><span class="p">();</span>
  92. </span><span class="line">
  93. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">item</span><span class="p">.</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="s1">'taggable'</span><span class="p">))</span>
  94. </span><span class="line"> <span class="k">continue</span><span class="p">;</span>
  95. </span><span class="line">
  96. </span><span class="line"> <span class="c1">// code bien long…</span>
  97. </span><span class="line"><span class="p">}</span>
  98. </span></code></pre></td></tr></table></div></figure>
  99. <p>Au passage, vous ne le savez peut-être pas mais <code>break</code> et <code>continue</code> autorisent le recours à un <em>label</em> pour court-circuiter au-delà de leur boucle conteneur immédiate, et remonter ainsi de plusieurs niveaux (mais toujours au sein de la fonction courante). Voici un exemple de recherche dans une matrice, hors la diagonale. On ignore la diagonale en court-circuitant le tour courant avec un <code>continue</code> simple, mais si on trouve la valeur recherchée, on court-circuite l’ensemble des boucles pour aller directement à la suite de la fonction, au moyen d’un label sur la boucle externe et de son emploi dans le <code>break</code> interne :</p>
  100. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  101. <span class="line-number">2</span>
  102. <span class="line-number">3</span>
  103. <span class="line-number">4</span>
  104. <span class="line-number">5</span>
  105. <span class="line-number">6</span>
  106. <span class="line-number">7</span>
  107. <span class="line-number">8</span>
  108. <span class="line-number">9</span>
  109. <span class="line-number">10</span>
  110. <span class="line-number">11</span>
  111. <span class="line-number">12</span>
  112. <span class="line-number">13</span>
  113. <span class="line-number">14</span>
  114. <span class="line-number">15</span>
  115. <span class="line-number">16</span>
  116. <span class="line-number">17</span>
  117. <span class="line-number">18</span>
  118. <span class="line-number">19</span>
  119. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">function</span> <span class="nx">searchGridExceptDiagonal</span><span class="p">(</span><span class="nx">grid</span><span class="p">,</span> <span class="nx">cell</span><span class="p">)</span> <span class="p">{</span>
  120. </span><span class="line"> <span class="kd">var</span> <span class="nx">result</span><span class="p">;</span>
  121. </span><span class="line">
  122. </span><span class="line"> <span class="nx">outer</span><span class="o">:</span> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">rowIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">rows</span> <span class="o">=</span> <span class="nx">grid</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">rowIndex</span> <span class="o">&lt;</span> <span class="nx">rows</span><span class="p">;</span> <span class="o">++</span><span class="nx">rowIndex</span><span class="p">)</span> <span class="p">{</span>
  123. </span><span class="line"> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">colIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">cols</span> <span class="o">=</span> <span class="nx">grid</span><span class="p">[</span><span class="nx">colIndex</span><span class="p">].</span><span class="nx">length</span><span class="p">;</span> <span class="nx">colIndex</span> <span class="o">&lt;</span> <span class="nx">cols</span><span class="p">;</span> <span class="o">++</span><span class="nx">colIndex</span><span class="p">)</span> <span class="p">{</span>
  124. </span><span class="line"> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">colIndex</span><span class="p">);</span>
  125. </span><span class="line">
  126. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">rowIndex</span> <span class="o">==</span> <span class="nx">colIndex</span><span class="p">)</span>
  127. </span><span class="line"> <span class="k">continue</span><span class="p">;</span>
  128. </span><span class="line">
  129. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">cell</span> <span class="o">===</span> <span class="nx">grid</span><span class="p">[</span><span class="nx">rowIndex</span><span class="p">][</span><span class="nx">colIndex</span><span class="p">])</span> <span class="p">{</span>
  130. </span><span class="line"> <span class="nx">result</span> <span class="o">=</span> <span class="p">[</span><span class="nx">rowIndex</span><span class="p">,</span> <span class="nx">colIndex</span><span class="p">];</span>
  131. </span><span class="line"> <span class="k">break</span> <span class="nx">outer</span><span class="p">;</span>
  132. </span><span class="line"> <span class="p">}</span>
  133. </span><span class="line"> <span class="p">}</span>
  134. </span><span class="line"> <span class="p">}</span>
  135. </span><span class="line">
  136. </span><span class="line"> <span class="k">return</span> <span class="nx">processResult</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
  137. </span><span class="line"><span class="p">}</span>
  138. </span></code></pre></td></tr></table></div></figure>
  139. <p>Enfin, il existe le fameux cas du « <code>return else</code> », un cas classique de superflu. Voyez plutôt :</p>
  140. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  141. <span class="line-number">2</span>
  142. <span class="line-number">3</span>
  143. <span class="line-number">4</span>
  144. <span class="line-number">5</span>
  145. <span class="line-number">6</span>
  146. <span class="line-number">7</span>
  147. <span class="line-number">8</span>
  148. <span class="line-number">9</span>
  149. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">function</span> <span class="nx">processItems</span><span class="p">(</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
  150. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
  151. </span><span class="line"> <span class="k">return</span> <span class="p">[];</span>
  152. </span><span class="line"> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  153. </span><span class="line"> <span class="kd">var</span> <span class="nx">result</span> <span class="o">=</span> <span class="p">[];</span>
  154. </span><span class="line"> <span class="c1">// code bien long…</span>
  155. </span><span class="line"> <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
  156. </span><span class="line"> <span class="p">}</span>
  157. </span><span class="line"><span class="p">}</span>
  158. </span></code></pre></td></tr></table></div></figure>
  159. <p>C’est parfaitement superflu, comme en atteste l’exemple plus haut : un <code>return</code> court-circuite la fonction conteneur, le <code>else</code> est donc sans intérêt : on peut utiliser directement le code qu’il contient.</p>
  160. <p>Bref : l’indentation c’est bien, mais en faire trop c’est moins bien ! N’abusons pas des bonnes choses…</p>
  161. <h2>2. Comparer avec les <em>rvalues</em> à gauche</h2>
  162. <p>Qu’est-ce qu’une <em>rvalue</em> ? En jargon d’analyse syntaxique (<em>parsing</em>), on désigne ainsi l’opérande droit d’un opérateur. Par symétrie, l’opérande gauche est appelé <em>lvalue</em>. Par exemple, dans <code>x == 42</code>, <code>x</code> est la <em>lvalue</em> et <code>42</code> la rvalue. Dans <code>str += 3</code>, ce seraient évidemment <code>str</code> et <code>3</code>, respectivement.</p>
  163. <p>Dans tous les langages où l’opérateur d’affectation (<code>=</code>) est un sous-ensemble de celui de comparaison (<code>==</code>), et où l’affectation est une expression, on risque de tomber dans le piège du <em>égale manquant</em>. Ainsi en JavaScript C, C++, Java, Ruby, etc. on peut se gaufrer comme suit :</p>
  164. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  165. <span class="line-number">2</span>
  166. <span class="line-number">3</span>
  167. <span class="line-number">4</span>
  168. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span>
  169. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">list</span><span class="p">[</span><span class="nx">index</span><span class="p">]</span> <span class="o">=</span> <span class="mi">42</span><span class="p">)</span> <span class="p">{</span>
  170. </span><span class="line"> <span class="c1">// code de traitement…</span>
  171. </span><span class="line"> <span class="p">}</span>
  172. </span></code></pre></td></tr></table></div></figure>
  173. <p>Vous avez vu la blague ? Le <code>=</code> au lieu du <code>==</code> ? C’est un piège fréquent même pour les développeurs chevronnés : après tout, c’est « juste » une faute de frappe. Mais c’est tellement discret que ça peut être infernal à déboguer.</p>
  174. <p>Si vous avez un bon outillage associé à votre éditeur de code, votre compilateur ou votre interpréteur, il vous fera probablement remarquer qu’il y a ici baleine sous gravier. C’est par exemple le cas de <a href="http://www.jslint.com/">JSLint</a> et <a href="http://www.jshint.com/">JSHint</a>, tous deux fournis par exemple dans Sublime Text 2 au moyen de l’excellent plugin <a href="https://github.com/SublimeLinter/SublimeLinter">SublimeLinter</a>.</p>
  175. <p>Vous avez alors le choix :</p>
  176. <ul>
  177. <li>soit il s’agit bien d’une affectation à la volée (pour utilisation de la valeur dans le bloc, par exemple), et vous pouvez rendre ça explicite en <em>doublant les parenthèses</em> ;</li>
  178. <li>soit c’est une comparaison et vous rajoutez un <code>=</code>.</li>
  179. </ul>
  180. <p>Toutefois, le souci principal c’est que ça puisse arriver (ce code s’exécute, il est valide ; il fait juste n’importe quoi…) alors que vous visiez une comparaison. C’est pourquoi dans tous ces langages j’ai le réflexe d’inverser, pour les comparaisons d’égalité uniquement (<code>==</code> et <code>===</code>, en JavaScript), la <em>lvalue</em> et la <em>rvalue</em>. Ça donnerait ceci :</p>
  181. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  182. <span class="line-number">2</span>
  183. <span class="line-number">3</span>
  184. <span class="line-number">4</span>
  185. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">list</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span>
  186. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="mi">42</span> <span class="o">==</span> <span class="nx">list</span><span class="p">[</span><span class="nx">index</span><span class="p">])</span> <span class="p">{</span>
  187. </span><span class="line"> <span class="c1">// code de traitement…</span>
  188. </span><span class="line"> <span class="p">}</span>
  189. </span></code></pre></td></tr></table></div></figure>
  190. <p>Voyez-vous ce qui se passe ? Si j’oublie un <code>=</code>, l’affectation résultante n’est pas valide : je ne peux pas modifier le litéral de gauche… Bien sûr, si je compare deux variables ou autres opérandes modifiables, ça ne m’aide pas. Et si j’opte normalement pour des égalités strictes (<code>===</code>), le risque de n’avoir qu’un <code>=</code> est bien moindre. Mais au global, c’est une habitude qui a sauvé un nombre incalculable de fois mes étudiants, stagiaires, collègues… et moi-même, naturellement.</p>
  191. <h2>3. Mettre en cache les expressions lourdes</h2>
  192. <p>Il existe de nombreux cas où une expression JS, aussi courte soit-elle, est lourde à exécuter.</p>
  193. <p>Certaines sont intuitivement lourdes, comme le <code>$(selector)</code> de jQuery (ce qui n’empêche pas les gens de faire quinze fois la même sélection dans leur fonction…) ou un appel à une de vos fonctions dont vous savez qu’elle mouline pas mal.</p>
  194. <p>D’autres sont plus subtiles ou surprenantes, comme les litéraux d’<a href="/2012/08/13/enfin-maitriser-les-expressions-rationnelles/">expressions rationnelles</a> ou les longueurs de tableaux.</p>
  195. <p>Mettre en cache, ou si vous préférez <em>memoizing</em> ces expressions peut avoir une influence notable sur vos performances dans un code utilisé intensivement.</p>
  196. <p>Commençons par les cas évidents :</p>
  197. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  198. <span class="line-number">2</span>
  199. <span class="line-number">3</span>
  200. <span class="line-number">4</span>
  201. <span class="line-number">5</span>
  202. <span class="line-number">6</span>
  203. <span class="line-number">7</span>
  204. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// BEUARK :</span>
  205. </span><span class="line"><span class="nx">$</span><span class="p">(</span><span class="s1">'#foo .bar'</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="nx">handleBarClick</span><span class="p">);</span>
  206. </span><span class="line"><span class="k">if</span> <span class="p">(</span><span class="nx">escapesToo</span><span class="p">)</span>
  207. </span><span class="line"> <span class="nx">$</span><span class="p">(</span><span class="s1">'#foo .bar'</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">'keypress'</span><span class="p">,</span> <span class="nx">handleEscapes</span><span class="p">);</span>
  208. </span><span class="line"><span class="nx">items</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">item</span><span class="p">)</span> <span class="p">{</span>
  209. </span><span class="line"> <span class="nx">$</span><span class="p">(</span><span class="s1">'#foo .bar'</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="nx">item</span><span class="p">);</span>
  210. </span><span class="line"><span class="p">});</span>
  211. </span></code></pre></td></tr></table></div></figure>
  212. <p>Ce type de code est plus fréquent qu’on ne croie, tant les développeurs préfèrent copier-coller que refactoriser leur code, aussi simple cela soit-il ici. Naturellement, conserver l’objet jQuery résultat est une bien <a href="http://jsperf.com/caching-jquery-selectors">meilleure</a> <a href="http://jsperf.com/caching-jquery-objects-perf/13">option</a> (ou recourir au chaînage, quand c’est possible) :</p>
  213. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  214. <span class="line-number">2</span>
  215. <span class="line-number">3</span>
  216. <span class="line-number">4</span>
  217. <span class="line-number">5</span>
  218. <span class="line-number">6</span>
  219. <span class="line-number">7</span>
  220. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">var</span> <span class="nx">scope</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#foo .bar'</span><span class="p">);</span>
  221. </span><span class="line"><span class="nx">scope</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="nx">handleBarClick</span><span class="p">);</span>
  222. </span><span class="line"><span class="k">if</span> <span class="p">(</span><span class="nx">escapesToo</span><span class="p">)</span>
  223. </span><span class="line"> <span class="nx">scope</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'keypress'</span><span class="p">,</span> <span class="nx">handleEscapes</span><span class="p">);</span>
  224. </span><span class="line"><span class="nx">items</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">item</span><span class="p">)</span> <span class="p">{</span>
  225. </span><span class="line"> <span class="nx">scope</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">item</span><span class="p">);</span>
  226. </span><span class="line"><span class="p">});</span>
  227. </span></code></pre></td></tr></table></div></figure>
  228. <p>Lorsque vous réalisez une fonction dont le résultat n’est pas censé changer pour une même série d’arguments, mais qui est susceptible d’être souvent appelée, il est souhaitable de recourir à la <em>memoization</em> pour cette fonction. Voyons un cas simple, sans arguments :</p>
  229. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  230. <span class="line-number">2</span>
  231. <span class="line-number">3</span>
  232. <span class="line-number">4</span>
  233. <span class="line-number">5</span>
  234. <span class="line-number">6</span>
  235. <span class="line-number">7</span>
  236. <span class="line-number">8</span>
  237. <span class="line-number">9</span>
  238. <span class="line-number">10</span>
  239. <span class="line-number">11</span>
  240. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">function</span> <span class="nx">heavyDuty</span><span class="p">()</span> <span class="p">{</span>
  241. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="kc">undefined</span> <span class="o">!==</span> <span class="nx">heavyDuty</span><span class="p">.</span><span class="nx">cached</span><span class="p">)</span>
  242. </span><span class="line"> <span class="k">return</span> <span class="nx">heavyDuty</span><span class="p">.</span><span class="nx">cached</span><span class="p">;</span>
  243. </span><span class="line">
  244. </span><span class="line"> <span class="kd">var</span> <span class="nx">result</span><span class="p">;</span>
  245. </span><span class="line">
  246. </span><span class="line"> <span class="c1">// Gros code bien lourd et compliqué, voire coûteux financièrement (appels API, toussa…)</span>
  247. </span><span class="line"> <span class="nx">heavyDuty</span><span class="p">.</span><span class="nx">cached</span> <span class="o">=</span> <span class="nx">result</span><span class="p">;</span>
  248. </span><span class="line">
  249. </span><span class="line"> <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
  250. </span><span class="line"><span class="p">}</span>
  251. </span></code></pre></td></tr></table></div></figure>
  252. <p>Si ça dépend des arguments, <code>cached</code> peut devenir un hash (<code>{}</code>) dont les clés sont des <code>String</code> construites sur base des arguments. On peut même génériser tout ça facilement et en faire un mixin. La bibliothèque <a href="http://underscorejs.org/">Underscore</a>, parmi ses myriades de fonctions cool, propose ainsi <a href="http://underscorejs.org/#memoize"><code>_.memoize</code></a> à cet effet.</p>
  253. <p>Plus subtil à présent : les expressions rationnelles.</p>
  254. <p>Lorsqu’on recourt à une regex assez courte, il est fréquent de se contenter de la copier-coller, ou simplement de la laisser litérale au sein d’un boucle par exemple. C’est dommage, car alors on risque de la voir « compiler » à chaque utilisation, donc à chaque tour de boucle :</p>
  255. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  256. <span class="line-number">2</span>
  257. <span class="line-number">3</span>
  258. <span class="line-number">4</span>
  259. <span class="line-number">5</span>
  260. <span class="line-number">6</span>
  261. <span class="line-number">7</span>
  262. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// SAYMAL :</span>
  263. </span><span class="line"><span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">longArray</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
  264. </span><span class="line"> <span class="kd">var</span> <span class="nx">line</span> <span class="o">=</span> <span class="nx">longArray</span><span class="p">[</span><span class="nx">index</span><span class="p">];</span>
  265. </span><span class="line">
  266. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="sr">/[\s\u00a0][:;?!]/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">line</span><span class="p">))</span>
  267. </span><span class="line"> <span class="k">return</span> <span class="nx">line</span><span class="p">;</span>
  268. </span><span class="line"><span class="p">}</span>
  269. </span></code></pre></td></tr></table></div></figure>
  270. <p>Il est <a href="http://jsperf.com/putting-regexes-out-of-loops">préférable</a> de sortir la regex de la boucle, voire de la fonction et d’en faire une « constante » (en ES3, on n’a pas <code>const</code>, on utilisera donc <code>var</code>) :</p>
  271. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  272. <span class="line-number">2</span>
  273. <span class="line-number">3</span>
  274. <span class="line-number">4</span>
  275. <span class="line-number">5</span>
  276. <span class="line-number">6</span>
  277. <span class="line-number">7</span>
  278. <span class="line-number">8</span>
  279. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">var</span> <span class="nx">WHITESPACE_AND_DOUBLE_SIGN</span> <span class="o">=</span> <span class="sr">/[\s\u00a0][:;?!]/</span><span class="p">;</span>
  280. </span><span class="line">
  281. </span><span class="line"><span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">longArray</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
  282. </span><span class="line"> <span class="kd">var</span> <span class="nx">line</span> <span class="o">=</span> <span class="nx">longArray</span><span class="p">[</span><span class="nx">index</span><span class="p">];</span>
  283. </span><span class="line">
  284. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">WHITESPACE_AND_DOUBLE_SIGN</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">line</span><span class="p">))</span>
  285. </span><span class="line"> <span class="k">return</span> <span class="nx">line</span><span class="p">;</span>
  286. </span><span class="line"><span class="p">}</span>
  287. </span></code></pre></td></tr></table></div></figure>
  288. <p>Cela a en outre le double avantage d’expliciter le sens de la regex et de faciliter la vie à certaines colorations syntaxiques :-)</p>
  289. <p>Notez au passage une autre bonne pratique conjointe : lorsqu’on veut juste un résultat booléen sur la regex (correspondance ou non), sans s’intéresser au détail de la correspondance, on utilisera avec profit <code>regex.test(string)</code> plutôt que <code>string.match(regex)</code>. La première est <a href="http://jsperf.com/regexp-test-vs-match-m5">nettement plus performante</a> que la seconde, car elle renvoie juste un booléen et ne s’occupe pas de constituer des groupes, etc.</p>
  290. <p>Dernier cas évoqué ici : les longueurs de tableaux.</p>
  291. <p>Il est vain de vouloir deviner comment votre <em>runtime</em> JS représente en interne votre <code>Array</code> JavaScript. Cela dépend de la runtime, des types de valeur stockées et de la longueur du tableau. Suivant les cas, il peut s’agir d’un véritable tableau (zone de mémoire continue à indexation directe), d’une liste liée, voire d’une structure plus avancée.</p>
  292. <p>En conséquence, il n’est pas rare (surtout sur les vieux navigateurs) que le code en apparence bénin <code>myArray.length</code> soit en réalité coûteux, exigeant en interne le parcours intégral du tableau. Une boucle « classique » sur la longueur aurait donc une <a href="http://fr.wikipedia.org/wiki/Th%C3%A9orie_de_la_complexit%C3%A9_%28informatique_th%C3%A9orique%29#Complexit.C3.A9_en_temps_et_en_espace">complexité quadratique</a> :</p>
  293. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  294. <span class="line-number">2</span>
  295. <span class="line-number">3</span>
  296. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// RISKY</span>
  297. </span><span class="line"><span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">myArray</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span>
  298. </span><span class="line"> <span class="c1">// …</span>
  299. </span></code></pre></td></tr></table></div></figure>
  300. <p>Dans la mesure où les boucles sur des tableaux dont la longueur change au fil des tours sont rarissimes (et sources de bien des bugs), il est toujours préférable de mettre la longueur (fixe) « en cache » dans une variable, au sein de la section d’initialisation de la boucle :</p>
  301. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  302. <span class="line-number">2</span>
  303. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">myArray</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span>
  304. </span><span class="line"> <span class="c1">// …</span>
  305. </span></code></pre></td></tr></table></div></figure>
  306. <p>Naturellement, si vous passez par un itérateur (ceux d’ES5 : <code>forEach</code>, <code>map</code>, <code>some</code>… ou ceux d’Underscore, de jQuery, etc.) vous pouvez leur faire confiance pour avoir fait au mieux.</p>
  307. <h2>4. Utiliser la délégation d’événements plutôt que des tas de gestionnaires étroits</h2>
  308. <p>On est là aussi dans les trucs rabâchés depuis bien 5 ans, mais je vois toujours plein de gens qui font le contraire, alors revenons-y.</p>
  309. <p>Dans une page web, vous associez des gestionnaires d’événements (<em>event handlers</em>) à des événements sur divers endroits de la page. Le réflexe initial consiste à les associer « au plus près », sur l’élément lui-même en général.</p>
  310. <p>La plupart du temps, ça ne pose aucun souci, c’est même une bonne idée.</p>
  311. <p>Mais il y a au moins deux situations dans lesquelles c’est soit super lourd, soit carrément cassé.</p>
  312. <p>Le premier cas, c’est lorsque vous écoutez le même événement sur pléthore d’éléments. Par exemple, tous les éléments d’une liste ; toutes les lignes d’un corps de tableau ; tous les liens porteurs d’une classe CSS donnée… Attacher un gestionnaire par élément, alors que le comportement est partagé, est inutilement lourd et peut vite <a href="http://jsperf.com/jquery-event-delegation/5">dégrader les performances</a> quand le nombre d’éléments est élevé.</p>
  313. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  314. <span class="line-number">2</span>
  315. <span class="line-number">3</span>
  316. <span class="line-number">4</span>
  317. <span class="line-number">5</span>
  318. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// Un gestionnaire par élément (beuark) :</span>
  319. </span><span class="line"><span class="nx">$</span><span class="p">(</span><span class="s1">'ul#files li'</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
  320. </span><span class="line"> <span class="kd">var</span> <span class="nx">item</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
  321. </span><span class="line"> <span class="c1">// …</span>
  322. </span><span class="line"><span class="p">});</span>
  323. </span></code></pre></td></tr></table></div></figure>
  324. <p>On préfère alors tirer parti du <em>bubbling</em> (le fait que les événements « bouillonnent » au travers du DOM, d’un élément vers son parent et ainsi de suite jusqu’au document) et attacher le gestionnaire au plus proche commun parent (souvent le document, mais ça peut être bien plus ciblé, comme la liste qui contient tous les éléments souhaités). On aura alors recours à une syntaxe de capture appropriée. Par exemple :</p>
  325. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  326. <span class="line-number">2</span>
  327. <span class="line-number">3</span>
  328. <span class="line-number">4</span>
  329. <span class="line-number">5</span>
  330. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// Un gestionnaire délégué pour tous les éléments :</span>
  331. </span><span class="line"><span class="nx">$</span><span class="p">(</span><span class="s1">'ul#files'</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="s1">'li'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
  332. </span><span class="line"> <span class="kd">var</span> <span class="nx">item</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
  333. </span><span class="line"> <span class="c1">// …</span>
  334. </span><span class="line"><span class="p">});</span>
  335. </span></code></pre></td></tr></table></div></figure>
  336. <p>(Dans les deux cas avec jQuery, <code>this</code> fera bien référence au <code>&lt;li&gt;</code>.)</p>
  337. <p>L’autre cas de figure où une connexion trop ciblée des gestionnaires d’événement est problématique concerne le chargement/remplacement de contenus HTML (le plus souvent <em>via</em> Ajax). Si on attache ces gestionnaires sur une zone de la page qui va se faire remplacer par la suite (au moyen d’un appel manuel à <code>.html(…)</code> ou <code>$.load(…)</code>, par exemple), ces gestionnaires resteront attachés aux anciens éléments désormais disparus, alors que les nouveaux éléments fraîchement injectés ne disposeront pas, eux, de gestionnaires d’événements.</p>
  338. <p>Il est alors préférable de déléguer ces gestionnaires au niveau du conteneur de la zone qui va évoluer (être remplacée/rechargée). jQuery assurant le bouillonnement même des événements qui, sur les vieux IE, ne bouillonnent pas (<code>focus</code>, <code>blur</code>, <code>change</code> et <code>submit</code>), cette solution est exploitable pour tous les événements voulus. Ainsi, nul besoin de les rattacher le moment venu, ou de nettoyer ceux installés avant le remplacement du contenu.</p>
  339. <h2>5. Isoler son code avec le <em>module pattern</em></h2>
  340. <p>Encore aujourd’hui, en 2013, l’immense majorité des développeurs JS côté navigateur pondent leur code <em>au niveau global</em>. Imaginons qu’on vous demande de notifier Google Analytics de tout téléchargement de fichier sur votre site, sachant que vous avez pris soin de coller une classe CSS <code>file</code> aux liens correspondants. Vous pourriez ajouter à votre page un script <code>track_downloads.js</code> qui ressemblerait à ceci :</p>
  341. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  342. <span class="line-number">2</span>
  343. <span class="line-number">3</span>
  344. <span class="line-number">4</span>
  345. <span class="line-number">5</span>
  346. <span class="line-number">6</span>
  347. <span class="line-number">7</span>
  348. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">function</span> <span class="nx">trackLink</span><span class="p">(</span><span class="nx">link</span><span class="p">)</span> <span class="p">{</span>
  349. </span><span class="line"> <span class="nx">_gaq</span><span class="p">.</span><span class="nx">push</span><span class="p">([</span><span class="s1">'_trackEvent'</span><span class="p">,</span> <span class="s1">'files'</span><span class="p">,</span> <span class="s1">'download'</span><span class="p">,</span> <span class="nx">link</span><span class="p">.</span><span class="nx">href</span><span class="p">]);</span>
  350. </span><span class="line"><span class="p">}</span>
  351. </span><span class="line">
  352. </span><span class="line"><span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="s1">'a.file'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
  353. </span><span class="line"> <span class="nx">trackLink</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
  354. </span><span class="line"><span class="p">});</span>
  355. </span></code></pre></td></tr></table></div></figure>
  356. <p>C’est un cas <em>light</em>, parce qu’en vérité, vos fichiers sont souvent multi-sujets, ou en tout cas « pourrissent le global » avec des tas de fonctions et variables partagées… (Si si, avouez, ça vous arrive, je le sais, je le vois tout le temps).</p>
  357. <p>Outre que ça pose souci le jour où vous utilisez un autre morceau de code qui lui aussi aurait une fonction globale <code>trackLink</code> (collision de noms : le dernier qui a parlé a raison), c’est juste dommage de polluer ainsi la portée globale <em>pour rien</em>. En effet, la majorité de vos codes sont en fait autonomes, ou comme disent les anglais <em>self-contained</em> : ils n’ont pas d’API publique à fournir, ils ont juste du code interne à leur fonctionnement, attaché à la page par gestionnaires d’événements.</p>
  358. <p>Il est dès lors préférable d’enrober votre fichier dans un module. Je ne parle pas forcément d’un « vrai » module (au sens <a href="http://requirejs.org/docs/whyamd.html">AMD</a> ou <a href="http://wiki.commonjs.org/wiki/Modules/1.1">CommonJS</a>, par exemple), mais au minimum du <em>module pattern</em>, qui est au cœur des autres et repose simplement sur une fonction immédiatement exécutée (<em>Immediately-Invoked Function Expression</em>, ou IIFE, en anglais) :</p>
  359. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  360. <span class="line-number">2</span>
  361. <span class="line-number">3</span>
  362. <span class="line-number">4</span>
  363. <span class="line-number">5</span>
  364. <span class="line-number">6</span>
  365. <span class="line-number">7</span>
  366. <span class="line-number">8</span>
  367. <span class="line-number">9</span>
  368. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">$</span><span class="p">,</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
  369. </span><span class="line"> <span class="kd">function</span> <span class="nx">trackLink</span><span class="p">(</span><span class="nx">link</span><span class="p">)</span> <span class="p">{</span>
  370. </span><span class="line"> <span class="nx">_gaq</span><span class="p">.</span><span class="nx">push</span><span class="p">([</span><span class="s1">'_trackEvent'</span><span class="p">,</span> <span class="s1">'files'</span><span class="p">,</span> <span class="s1">'download'</span><span class="p">,</span> <span class="nx">link</span><span class="p">.</span><span class="nx">href</span><span class="p">]);</span>
  371. </span><span class="line"> <span class="p">}</span>
  372. </span><span class="line">
  373. </span><span class="line"> <span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="s1">'a.file'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
  374. </span><span class="line"> <span class="nx">trackLink</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
  375. </span><span class="line"> <span class="p">});</span>
  376. </span><span class="line"><span class="p">})(</span><span class="nx">jQuery</span><span class="p">);</span>
  377. </span></code></pre></td></tr></table></div></figure>
  378. <p>Ici tout le code définit en fait une fonction anonyme, qu’il appelle immédiatement. En vertu des règles de portée et de <em>closure</em> de JavaScript, les déclarations de fonction et les <code>var</code> à l’intérieur d’une fonction lui sont privées : l’extérieur ne sait donc rien, par exemple, de <code>trackLink</code>.</p>
  379. <p>J’utilise ici au passage des petites astuces habituelles quant aux arguments de l’IIFE (elle peut très bien ne pas en avoir) :</p>
  380. <ul>
  381. <li>Au cas où jQuery serait utilisé en <a href="http://api.jquery.com/jQuery.noConflict/">mode noConflict</a>, je me permets tout de même d’y référer facilement avec <code>$</code> grâce à un argument dédié.</li>
  382. <li>Si jamais un petit malin a pourri <code>undefined</code> en collant par exemple un <code>undefined = 42;</code> quelque part, ça ne m’atteint pas car grâce à mon deuxième paramètre qui n’aura pas d’argument correspondant, dans mon « module », <code>undefined</code> est bien <code>undefined</code> (bon, ici on ne s’en sert pas, mais c’est un schéma désormais classique…).</li>
  383. </ul>
  384. <p>Si le sujet des <em>module patterns</em> et toutes les variations/évolutions possibles vous intéressent, <a href="http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth">cet article déjà ancien</a> de Ben Cherry, développeur front-end chez Twitter, est une excellente lecture.</p>
  385. <h2>6. Découpler son code grâce aux événements personnalisés</h2>
  386. <p>Le saviez-tu ? Ce n’est pas parce que du code externe a besoin de déclencher du code à toi que tu dois nécessairement en faire une API publique (c’est-à-dire des fonctions accessibles de l’extérieur). En fait, il existe notamment un cas de figure où une fonction publique n’est pas la bonne solution : la maintenance opérationnelle suite à un événement extérieur.</p>
  387. <p>« Gni ? »</p>
  388. <p>Imaginons que tu codes un module qui décore tout champ de type date avec un joli widget à toi (comment ça, tu n’utilises pas <a href="https://github.com/ChiperSoft/Kalendae">Kalendae</a> ?!). Tu as respecté les préceptes du point précédent (module opaque décorant automatiquement les <code>input[type=date], input.date</code> au chargement du DOM, par exemple), mais tu te retrouves soudain confronté à un problème : si quelqu’un met à jour la page plus tard (avec Ajax ou un système de templates…) les nouveaux champs ne seront pas décorés.</p>
  389. <p>Ni une ni deux, tu « exportes » donc une méthode publique, probablement collée en global au niveau de <code>window</code> ou d’un espace de noms dédié à ton module ; une méthode du genre <code>refreshDatePickers()</code>. Peut-être que ton code ressemble à ça, exploitant ce que Ben Cherry appelle la <em>loose augmentation</em> :</p>
  390. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  391. <span class="line-number">2</span>
  392. <span class="line-number">3</span>
  393. <span class="line-number">4</span>
  394. <span class="line-number">5</span>
  395. <span class="line-number">6</span>
  396. <span class="line-number">7</span>
  397. <span class="line-number">8</span>
  398. <span class="line-number">9</span>
  399. <span class="line-number">10</span>
  400. <span class="line-number">11</span>
  401. <span class="line-number">12</span>
  402. <span class="line-number">13</span>
  403. <span class="line-number">14</span>
  404. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">var</span> <span class="nx">TotoWidgets</span> <span class="o">=</span> <span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">exports</span><span class="p">)</span> <span class="p">{</span>
  405. </span><span class="line"> <span class="c1">// ton joli code bien planqué ici…</span>
  406. </span><span class="line">
  407. </span><span class="line"> <span class="kd">function</span> <span class="nx">refreshDatePickers</span><span class="p">()</span> <span class="p">{</span>
  408. </span><span class="line"> <span class="c1">// …</span>
  409. </span><span class="line"> <span class="p">}</span>
  410. </span><span class="line">
  411. </span><span class="line"> <span class="nx">$</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
  412. </span><span class="line"> <span class="c1">// ton init ici…</span>
  413. </span><span class="line"> <span class="p">});</span>
  414. </span><span class="line">
  415. </span><span class="line"> <span class="nx">exports</span><span class="p">.</span><span class="nx">refreshDatePickers</span> <span class="o">=</span> <span class="nx">refreshDatePickers</span><span class="p">;</span>
  416. </span><span class="line"> <span class="k">return</span> <span class="nx">exports</span><span class="p">;</span>
  417. </span><span class="line"><span class="p">})(</span><span class="nx">TotoWidgets</span> <span class="o">||</span> <span class="p">{});</span>
  418. </span></code></pre></td></tr></table></div></figure>
  419. <p>Mais c’est dommage. On est ici dans le cas typique où :</p>
  420. <ul>
  421. <li>ta méthode ne renvoie rien</li>
  422. <li>elle est en fait, conceptuellement, un gestionnaire d’événement… pour un événement « un morceau de la page a changé » qui n’existe pas (ou pas forcément)</li>
  423. </ul>
  424. <p>Une solution plus discrète (dans le sens où elle ne te force pas à publier quelque API que ce soit) consisterait à utiliser un <em>événement personnalisé</em>, que tu déclencherais par exemple au niveau de la zone qui a changé (avec un bête <code>.trigger</code>), et que ton module écouterait au niveau document. Ça donnerait quelque chose comme ça :</p>
  425. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  426. <span class="line-number">2</span>
  427. <span class="line-number">3</span>
  428. <span class="line-number">4</span>
  429. <span class="line-number">5</span>
  430. <span class="line-number">6</span>
  431. <span class="line-number">7</span>
  432. <span class="line-number">8</span>
  433. <span class="line-number">9</span>
  434. <span class="line-number">10</span>
  435. <span class="line-number">11</span>
  436. <span class="line-number">12</span>
  437. <span class="line-number">13</span>
  438. <span class="line-number">14</span>
  439. <span class="line-number">15</span>
  440. <span class="line-number">16</span>
  441. <span class="line-number">17</span>
  442. <span class="line-number">18</span>
  443. <span class="line-number">19</span>
  444. <span class="line-number">20</span>
  445. <span class="line-number">21</span>
  446. <span class="line-number">22</span>
  447. <span class="line-number">23</span>
  448. <span class="line-number">24</span>
  449. <span class="line-number">25</span>
  450. <span class="line-number">26</span>
  451. <span class="line-number">27</span>
  452. <span class="line-number">28</span>
  453. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// Dans ton module de widget :</span>
  454. </span><span class="line"><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
  455. </span><span class="line"> <span class="kd">var</span> <span class="nx">knownWidgets</span> <span class="o">=</span> <span class="p">{};</span>
  456. </span><span class="line">
  457. </span><span class="line"> <span class="kd">function</span> <span class="nx">initWidgets</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
  458. </span><span class="line"> <span class="kd">var</span> <span class="nx">zone</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span> <span class="o">||</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">);</span>
  459. </span><span class="line">
  460. </span><span class="line"> <span class="nx">zone</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s1">'input[type=date], input.date'</span><span class="p">).</span><span class="nx">each</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">element</span><span class="p">)</span> <span class="p">{</span>
  461. </span><span class="line"> <span class="nx">element</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">id</span> <span class="o">||</span> <span class="nx">_</span><span class="p">.</span><span class="nx">uniqueId</span><span class="p">(</span><span class="s1">'datePicker'</span><span class="p">);</span>
  462. </span><span class="line">
  463. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">knownWidgets</span><span class="p">[</span><span class="nx">element</span><span class="p">.</span><span class="nx">id</span><span class="p">])</span>
  464. </span><span class="line"> <span class="k">return</span><span class="p">;</span>
  465. </span><span class="line">
  466. </span><span class="line"> <span class="nx">knownWidgets</span><span class="p">[</span><span class="nx">element</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MyDatePickerWidget</span><span class="p">(</span><span class="nx">element</span><span class="p">);</span>
  467. </span><span class="line"> <span class="p">});</span>
  468. </span><span class="line"> <span class="p">}</span>
  469. </span><span class="line">
  470. </span><span class="line"> <span class="nx">$</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
  471. </span><span class="line"> <span class="nx">initWidgets</span><span class="p">();</span>
  472. </span><span class="line"> <span class="p">});</span>
  473. </span><span class="line"> <span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">on</span><span class="p">(</span><span class="s1">'pageupdate.ui'</span><span class="p">,</span> <span class="nx">initWidgets</span><span class="p">);</span>
  474. </span><span class="line"><span class="p">})();</span>
  475. </span><span class="line">
  476. </span><span class="line">
  477. </span><span class="line"><span class="c1">// Dans le code qui modifie un morceau de la page (ex. Ajax) :</span>
  478. </span><span class="line"><span class="nx">$</span><span class="p">(</span><span class="s1">'ul#files'</span><span class="p">).</span><span class="nx">load</span><span class="p">(</span><span class="s1">'/files'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
  479. </span><span class="line"> <span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">trigger</span><span class="p">(</span><span class="s1">'pageupdate.ui'</span><span class="p">);</span>
  480. </span><span class="line"><span class="p">});</span>
  481. </span></code></pre></td></tr></table></div></figure>
  482. <p>Dans l’idéal, tu « standardises » ces événements custom à travers toute la couche UI de ton code JavaScript, de sorte que ça devient une sorte d’habitude pour tous tes widgets internes, etc. En fait, ce n’est ni plus ni moins que du <a href="http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern">publish/subscribe</a> (ou si tu préfères, <a href="http://fr.wikipedia.org/wiki/Observateur_(patron_de_conception">observateur/observé</a>)), mais avec le bouillonnement en plus, et donc la notion de zone DOM concernée.</p>
  483. <h2>7. Proposer un hash d’options plutôt qu’une signature à rallonge</h2>
  484. <p>Lorsqu’on écrit une masse minimum de code (à partir de quelques fonctions, surtout si elles sont publiques), il est toujours bénéfique de réfléchir à l’<em>API design</em>, c’est-à-dire à la conception générale de l’API ainsi produite. Qui utilisera ce code ? Comment ? Pour quels besoins ? Dans quels contextes ? Ce type de recul et de mise en perspective est hélas trop rare, même chez les designers d’API chevronnés, comme en attestent nombre d’exemples proéminents. Pour n’en citer que quelques-uns :</p>
  485. <ul>
  486. <li><code>jQuery.each</code>, le seul itérateur à ma connaissance à coller l’<em>index</em> en premier plutôt que la valeur, source à lui seul de semaines-hommes entières perdues chaque jour à l’échelle mondiale…</li>
  487. <li>L’API XSL/T du JDK ; le cas dominant (90% du besoin) étant l’application d’une feuille XSLT sur un document XML, cette situation requiert… une bonne douzaine de lignes de code.</li>
  488. <li>Dans le même esprit, le module <code>Net::HTTP</code> de Ruby. Prendre en charge HTTPS ou simplement traverser les redirections, deux besoins ultra-basiques, nécessitent quelques lignes de code non trivial.</li>
  489. <li><code>XMLHttpRequest</code> : outre sa casse discutable (XML en majuscules mais Http en casse Camel ?!), le cas de base Ajax nécessite une demi-douzaine de lignes de code, ce qui a immédiatement entraîné nombre de wrappers, au point qu’aujourd’hui peut-être un développeur front-end sur mille connait l’API d’origine…</li>
  490. <li>Au bas mot la moitié de l’API standard PHP : entre les paramètres jamais placés dans l’ordre intuitif, les casses variables, les ordonnancements aléatoires dans les noms de fonctions, etc. c’est un des plus emblématiques foutoirs du développement contemporain.</li>
  491. </ul>
  492. <p>Bien concevoir une API change radicalement sa facilité de découverte et d’apprentissage, et le plaisir qu’on a à l’employer. Je ne saurais trop vous conseiller à cet égard l’excellente conférence de Jake Archibald à Paris Web 2010 : <a href="http://www.dailymotion.com/video/xgmaoy_reusable-code-for-good-or-for-awesome-jake-archibald_tech">Reusable Code: For Good or For Awesome</a>, qui regorge d’exemples concrets et d’humour ravageur.</p>
  493. <p>Un des points les plus critiques du design d’une API reste pour moi la signature des fonctions/méthodes publiques. En particulier le passage à un hash d’options au lieu de paramètres positionnés dès lors qu’on dépasse, disons, 3 paramètres, ou que ceux-ci ont le même type de données attendu. Ainsi, on obtient un effet similaire aux paramètres nommés dans certains langages, ce qui fait que les <em>appels</em> à la fonction sont en quelque sorte « auto-documentés ».</p>
  494. <p>Personne n’aime tomber sur du code de ce genre :</p>
  495. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  496. <span class="line-number">2</span>
  497. <span class="line-number">3</span>
  498. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="nx">Toolkit</span><span class="p">.</span><span class="nx">initWidgets</span><span class="p">(</span><span class="s1">'container'</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
  499. </span><span class="line">
  500. </span><span class="line"><span class="nx">bindConnectors</span><span class="p">(</span><span class="mi">25</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="s1">'foo'</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>
  501. </span></code></pre></td></tr></table></div></figure>
  502. <p>Quand on tombe sur ce code, que ce soit celui d’un autre ou le nôtre il y a 3 mois (ce qui revient au même), on se demande automatiquement « mais ?! Ça fait quoi ce <code>true</code> ? », et ce pour pratiquement chaque paramètre.</p>
  503. <p>On préfèrerait tous tomber sur ceci :</p>
  504. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  505. <span class="line-number">2</span>
  506. <span class="line-number">3</span>
  507. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="nx">Toolkit</span><span class="p">.</span><span class="nx">initWidgets</span><span class="p">(</span><span class="s1">'container'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">autoBind</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">hideNative</span><span class="o">:</span> <span class="kc">false</span> <span class="p">});</span>
  508. </span><span class="line">
  509. </span><span class="line"><span class="nx">bindConnectors</span><span class="p">({</span> <span class="nx">interval</span><span class="o">:</span> <span class="mi">25</span><span class="p">,</span> <span class="nx">separator</span><span class="o">:</span> <span class="s1">''</span><span class="p">,</span> <span class="nx">container</span><span class="o">:</span> <span class="s1">'foo'</span><span class="p">,</span> <span class="nx">autoRefresh</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">delay</span><span class="o">:</span> <span class="mi">1000</span> <span class="p">});</span>
  510. </span></code></pre></td></tr></table></div></figure>
  511. <p>Dans le même esprit, ce n’est pas parce qu’une fonction propose une trentaine d’options que je dois <em>me les fader à chaque fois</em>. De ce point de vue, les fonctionnalités Ajax de jQuery sont un excellent exemple de conception pratique : <code>$.ajax</code> et ses aliases/wrappers, tels <code>$.get</code> ou <code>$(…).load</code>, sont tous conçus avec un hash d’options et des valeurs par défaut bien adaptées à la majorité des cas.</p>
  512. <p>Mettre en place ce type de fonction ne nécessite pas forcément plus de travail que pour une fonction à la signature plus directe. Parfois, ça en nécessite même moins.</p>
  513. <p>Avant d’écrire le code à l’intérieur d’une fonction à la signature non triviale, je vous encourage à la documenter immédiatement. Cash, juste au-dessus de la déclaration, dans un commentaire. Et surtout, <strong>surtout</strong>, mettez-y des <strong>exemples concrets d’appels</strong>. Ce n’est qu’en vous forçant ainsi à considérer les choses du point de vue « code appelant » (utilisateur, donc) que vous verrez rapidement si la signature que vous aviez initialement imaginée est adaptée ou doit être repensée. Et c’est à ce moment-là, alors qu’aucune ligne de code n’a été écrite encore dans la fonction, que le <em>refactoring</em> de la signature n’a aucun coût supplémentaire.</p>
  514. <p>Ensuite, écrire une telle fonction repose sur quelques principes simples :</p>
  515. <ul>
  516. <li>passer à un hash d’options dès qu’on dépasse 3-4 paramètres, ou qu’on a au moins deux paramètres de même type (donc ambigüs à l’appel)</li>
  517. <li>fournir des valeurs par défaut clairement regroupées et explicitées pour toutes les options, voire pour certains paramètres.</li>
  518. <li>si on propose plusieurs signatures (de la plus courte pour les cas majoritaires à la plus longue pour un cas sur-mesure), normaliser vers la signature longue avant le reste du code, afin de n’écrire qu’une version de l’implémentation.</li>
  519. </ul>
  520. <p>Prenons pour exemple une fonction <code>callAjax</code> dans l’esprit de celle de jQuery. En documentant ses appels avant de l’implémenter, on isole plusieurs types d’appel afin d’être pratique à appeler pour tous :</p>
  521. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  522. <span class="line-number">2</span>
  523. <span class="line-number">3</span>
  524. <span class="line-number">4</span>
  525. <span class="line-number">5</span>
  526. <span class="line-number">6</span>
  527. <span class="line-number">7</span>
  528. <span class="line-number">8</span>
  529. <span class="line-number">9</span>
  530. <span class="line-number">10</span>
  531. <span class="line-number">11</span>
  532. <span class="line-number">12</span>
  533. <span class="line-number">13</span>
  534. <span class="line-number">14</span>
  535. <span class="line-number">15</span>
  536. <span class="line-number">16</span>
  537. <span class="line-number">17</span>
  538. <span class="line-number">18</span>
  539. <span class="line-number">19</span>
  540. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// Makes an Ajax call. This can be called in a very concise way, with just the endpoint</span>
  541. </span><span class="line"><span class="c1">// URL, if you don't care about callbacks or behavior changes (the URL will be called through</span>
  542. </span><span class="line"><span class="c1">// HTTP GET). You can also specify just a success callback, or a success and an error, or</span>
  543. </span><span class="line"><span class="c1">// provide a full-blown options hash to fine-tune the behavior.</span>
  544. </span><span class="line"><span class="c1">//</span>
  545. </span><span class="line"><span class="c1">// Example calls:</span>
  546. </span><span class="line"><span class="c1">//</span>
  547. </span><span class="line"><span class="c1">// callAjax('/files')</span>
  548. </span><span class="line"><span class="c1">// callAjax('/files', function success(res) { … })</span>
  549. </span><span class="line"><span class="c1">// callAjax('/files', successCallback, errorCallback)</span>
  550. </span><span class="line"><span class="c1">// callAjax('/files', {</span>
  551. </span><span class="line"><span class="c1">// method: 'POST',</span>
  552. </span><span class="line"><span class="c1">// dataType: 'json',</span>
  553. </span><span class="line"><span class="c1">// success: function ajaxSuccess() { … },</span>
  554. </span><span class="line"><span class="c1">// error: function ajaxError() { … }</span>
  555. </span><span class="line"><span class="c1">// })</span>
  556. </span><span class="line"><span class="kd">function</span> <span class="nx">callAjax</span><span class="p">()</span> <span class="p">{</span>
  557. </span><span class="line">
  558. </span><span class="line"><span class="p">}</span>
  559. </span></code></pre></td></tr></table></div></figure>
  560. <p>Pour une signature polymorphe comme ici, on a plusieurs choix quant à la signature formelle (la liste des <em>paramètres</em>) de la fonction :</p>
  561. <ul>
  562. <li>ne rien mettre et tout traiter dynamiquement dans la fonction <em>via</em> <code>arguments</code></li>
  563. <li>ne poser que les paramètres invariables (ici l’URL) et gérer le reste via <code>arguments</code></li>
  564. <li>faire un mix des deux…</li>
  565. </ul>
  566. <p>Nous allons opter ici pour la deuxième approche. L’idée est de normaliser l’appel vers sa version la plus complexe, donc avec un hash d’options. On commencerait donc comme ceci :</p>
  567. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  568. <span class="line-number">2</span>
  569. <span class="line-number">3</span>
  570. <span class="line-number">4</span>
  571. <span class="line-number">5</span>
  572. <span class="line-number">6</span>
  573. <span class="line-number">7</span>
  574. <span class="line-number">8</span>
  575. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">function</span> <span class="nx">callAjax</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
  576. </span><span class="line"> <span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="nx">arguments</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">||</span> <span class="p">{};</span>
  577. </span><span class="line">
  578. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">_</span><span class="p">.</span><span class="nx">isFunction</span><span class="p">(</span><span class="nx">options</span><span class="p">))</span> <span class="p">{</span>
  579. </span><span class="line"> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">success</span><span class="o">:</span> <span class="nx">options</span><span class="p">,</span> <span class="nx">error</span><span class="o">:</span> <span class="nx">arguments</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="p">};</span>
  580. </span><span class="line"> <span class="p">}</span>
  581. </span><span class="line"> <span class="c1">// …</span>
  582. </span><span class="line"><span class="p">}</span>
  583. </span></code></pre></td></tr></table></div></figure>
  584. <p>On gère ainsi les cas suivants :</p>
  585. <ul>
  586. <li>pas de deuxième argument (ou deuxième argument <em>falsy</em>, ce qui n’est pas valide pour notre signature) : <code>options</code> est un objet vide (<code>{}</code>)</li>
  587. <li>le deuxième argument est une fonction : il s’agit alors du callback de succès, éventuellement suivi de celui d’erreur : on normalise vers un hash d’options avec les deux clés appropriées. Faute de troisième argument, <code>options.error</code> sera présent mais vaudra <code>undefined</code>.</li>
  588. <li>le deuxième argument est un hash d’options : on n’y touche pas.</li>
  589. </ul>
  590. <p>Pour gérer les valeurs par défaut, il suffit de définir un conteneur pour toutes celles-ci et de fusionner dans <code>options</code> toutes les clés absentes ou dont la valeur est <code>undefined</code>. On documente en général les valeurs possibles pour chaque option en écrivant le conteneur. Un emplacement plutôt valable pour celui-ci est une propriété <code>defaults</code> ou <code>defaultOptions</code> sur la fonction :</p>
  591. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  592. <span class="line-number">2</span>
  593. <span class="line-number">3</span>
  594. <span class="line-number">4</span>
  595. <span class="line-number">5</span>
  596. <span class="line-number">6</span>
  597. <span class="line-number">7</span>
  598. <span class="line-number">8</span>
  599. <span class="line-number">9</span>
  600. <span class="line-number">10</span>
  601. <span class="line-number">11</span>
  602. <span class="line-number">12</span>
  603. <span class="line-number">13</span>
  604. <span class="line-number">14</span>
  605. <span class="line-number">15</span>
  606. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="nx">callAjax</span><span class="p">.</span><span class="nx">defaults</span> <span class="o">=</span> <span class="p">{</span>
  607. </span><span class="line"> <span class="c1">// Possible data types are 'html', 'json', 'js', 'jsonp' and 'xml'</span>
  608. </span><span class="line"> <span class="nx">dataType</span><span class="o">:</span> <span class="s1">'html'</span><span class="p">,</span>
  609. </span><span class="line">
  610. </span><span class="line"> <span class="c1">// Error callback. Called with the response text and XHR object.</span>
  611. </span><span class="line"> <span class="nx">error</span><span class="o">:</span> <span class="nx">$</span><span class="p">.</span><span class="nx">noop</span><span class="p">,</span>
  612. </span><span class="line">
  613. </span><span class="line"> <span class="c1">// HTTP verb. If not 'GET' or 'POST', will emulate using a 'POST' and injecting</span>
  614. </span><span class="line"> <span class="c1">// a `_method` parameter with the passed verb.</span>
  615. </span><span class="line"> <span class="nx">method</span><span class="o">:</span> <span class="s1">'GET'</span><span class="p">,</span>
  616. </span><span class="line">
  617. </span><span class="line"> <span class="c1">// Success callback. Called with the response text (or parsed object according to</span>
  618. </span><span class="line"> <span class="c1">// `dataType`) and XHR object.</span>
  619. </span><span class="line"> <span class="nx">success</span><span class="o">:</span> <span class="nx">$</span><span class="p">.</span><span class="nx">noop</span>
  620. </span><span class="line"><span class="p">};</span>
  621. </span></code></pre></td></tr></table></div></figure>
  622. <p>Il est ainsi facile pour l’utilisateur de modifier globalement les valeurs par défaut. Si on souhaite interdire ça par mesure de sécurité/compatibilité, et qu’on est sur une <em>runtime</em> ES5, on peut toujours faire suivre la définition d’un <code>Object.freeze(callAjax.defaults)</code>. Une autre approche consisterait à enfermer la fonction et ses défauts dans une <em>closure</em> et ne publier que la fonction.</p>
  623. <p>Pour exploiter ces paramètres par défaut, rien de plus simple. Une fois <code>options</code> normalisé dans la fonction, on procède ainsi :</p>
  624. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  625. <span class="line-number">2</span>
  626. <span class="line-number">3</span>
  627. <span class="line-number">4</span>
  628. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">opt</span> <span class="k">in</span> <span class="nx">callAjax</span><span class="p">.</span><span class="nx">defaults</span><span class="p">)</span> <span class="p">{</span>
  629. </span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nx">_</span><span class="p">.</span><span class="nx">isUndefined</span><span class="p">(</span><span class="nx">options</span><span class="p">[</span><span class="nx">opt</span><span class="p">]))</span>
  630. </span><span class="line"> <span class="nx">options</span><span class="p">[</span><span class="nx">opt</span><span class="p">]</span> <span class="o">=</span> <span class="nx">callAjax</span><span class="p">.</span><span class="nx">defaults</span><span class="p">[</span><span class="nx">opt</span><span class="p">];</span>
  631. </span><span class="line"><span class="p">};</span>
  632. </span></code></pre></td></tr></table></div></figure>
  633. <p>En fait, ce type de schéma est si fréquent qu’il porte un nom usuel : <code>extend</code>. On en trouve des implémentations globalement équivalentes dans la majorité des bibliothèques, de jQuery à Prototype en passant par Underscore. Du coup, le code plus fréquent pour ce type de besoin est le suivant :</p>
  634. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  635. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="nx">options</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">extend</span><span class="p">({},</span> <span class="nx">callAjax</span><span class="p">.</span><span class="nx">defaults</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
  636. </span></code></pre></td></tr></table></div></figure>
  637. <p>Le reste du code de la fonction n’a plus qu’à exploiter les arguments fixes (<code>url</code>) et le contenu d’<code>options</code>.</p>
  638. <h2>8. Ne pas concaténer en masse les chaînes</h2>
  639. <p>En JavaScript il est fréquent de devoir construire petit à petit une chaîne de caractères massive ; il s’agit le plus souvent de HTML qu’on compose au fil de l’eau, à la main faute de mécanisme de <em>templating</em>. Vous savez, ce genre de code :</p>
  640. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  641. <span class="line-number">2</span>
  642. <span class="line-number">3</span>
  643. <span class="line-number">4</span>
  644. <span class="line-number">5</span>
  645. <span class="line-number">6</span>
  646. <span class="line-number">7</span>
  647. <span class="line-number">8</span>
  648. <span class="line-number">9</span>
  649. <span class="line-number">10</span>
  650. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// BEUARK</span>
  651. </span><span class="line"><span class="kd">function</span> <span class="nx">buildHTML</span><span class="p">(</span><span class="nx">names</span><span class="p">)</span> <span class="p">{</span>
  652. </span><span class="line"> <span class="kd">var</span> <span class="nx">result</span> <span class="o">=</span> <span class="s1">'&lt;ul&gt;'</span><span class="p">;</span>
  653. </span><span class="line">
  654. </span><span class="line"> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">names</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span>
  655. </span><span class="line"> <span class="nx">result</span> <span class="o">+=</span> <span class="s1">'&lt;li&gt;'</span> <span class="o">+</span> <span class="nx">_</span><span class="p">.</span><span class="nx">escape</span><span class="p">(</span><span class="nx">names</span><span class="p">[</span><span class="nx">index</span><span class="p">])</span> <span class="o">+</span> <span class="s1">'&lt;/li&gt;'</span><span class="p">;</span>
  656. </span><span class="line"> <span class="nx">result</span> <span class="o">+=</span> <span class="s1">'&lt;/ul&gt;'</span><span class="p">;</span>
  657. </span><span class="line">
  658. </span><span class="line"> <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
  659. </span><span class="line"><span class="p">}</span>
  660. </span></code></pre></td></tr></table></div></figure>
  661. <p>Ce genre de code est très peu performant. C’est comme en Java : une <code>String</code> est non modifiable, et du coup <code>+=</code> entraîne à chaque fois la création d’une nouvelle chaîne, ce qui implique qu’il faudra nettoyer la mémoire occupée par la précédente. Et plus c’est long, plus c’est lourd.</p>
  662. <p>Tout comme on recommande en Java de recourir à <code>StringBuffer</code> voire <code>StringBuilder</code>, en JavaScript la meilleure approche consiste à enquiller les fragments dans un tableau, pour au final faire le <code>join</code> qui regroupe tout ça :</p>
  663. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  664. <span class="line-number">2</span>
  665. <span class="line-number">3</span>
  666. <span class="line-number">4</span>
  667. <span class="line-number">5</span>
  668. <span class="line-number">6</span>
  669. <span class="line-number">7</span>
  670. <span class="line-number">8</span>
  671. <span class="line-number">9</span>
  672. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="kd">function</span> <span class="nx">buildHTML</span><span class="p">(</span><span class="nx">names</span><span class="p">)</span> <span class="p">{</span>
  673. </span><span class="line"> <span class="kd">var</span> <span class="nx">result</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'&lt;ul&gt;'</span><span class="p">];</span>
  674. </span><span class="line">
  675. </span><span class="line"> <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">names</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">index</span> <span class="o">&lt;</span> <span class="nx">len</span><span class="p">;</span> <span class="o">++</span><span class="nx">index</span><span class="p">)</span>
  676. </span><span class="line"> <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s1">'&lt;li&gt;'</span> <span class="o">+</span> <span class="nx">_</span><span class="p">.</span><span class="nx">escape</span><span class="p">(</span><span class="nx">names</span><span class="p">[</span><span class="nx">index</span><span class="p">])</span> <span class="o">+</span> <span class="s1">'&lt;/li&gt;'</span><span class="p">);</span>
  677. </span><span class="line"> <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s1">'&lt;/ul&gt;'</span><span class="p">);</span>
  678. </span><span class="line">
  679. </span><span class="line"> <span class="k">return</span> <span class="nx">result</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s1">''</span><span class="p">);</span> <span class="c1">// ou join('\n')</span>
  680. </span><span class="line"><span class="p">}</span>
  681. </span></code></pre></td></tr></table></div></figure>
  682. <p>Les performances n’ont <a href="http://jsperf.com/string-vs-array-concat">rien à voir</a>, surtout sur de grandes quantités. Et pour les petites, le code n’est pas plus compliqué, alors autant normaliser…</p>
  683. <h2>9. Réutiliser ses gros tableaux</h2>
  684. <p>Il arrive de temps en temps qu’on ait fini de bosser sur un tableau et qu’on veuille repartir sur un « tableau neuf ». Parfois, ce sont de <em>très gros</em> tableaux, comme par exemple ceux qu’on utilise pour calculer un filtre sur les pixels d’un <code>&lt;canvas&gt;</code>… On a alors tendance à faire :</p>
  685. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  686. <span class="line-number">2</span>
  687. <span class="line-number">3</span>
  688. <span class="line-number">4</span>
  689. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// travail sur un tableau "arr" qui devient maousse…</span>
  690. </span><span class="line">
  691. </span><span class="line"><span class="c1">// « remise à zéro » -- SAYBOF</span>
  692. </span><span class="line"><span class="nx">arr</span> <span class="o">=</span> <span class="p">[];</span>
  693. </span></code></pre></td></tr></table></div></figure>
  694. <p>En fait, cette affectation est sans surprise : elle envoie l’ancien tableau à la casse (il faudra en récupérer la mémoire) et crée un nouveau tableau, tout petit, qui devra donc lui aussi demander toujours davantage de mémoire au fil de son utilisation. C’est un peu dommage d’avoir basardé toute cette RAM alors qu’on en aura sans doute besoin juste après. Et jouer avec <code>new Array(length)</code> ne change rien à l’affaire : ça ne préalloue pas la mémoire pour autant.</p>
  695. <p>Mais surprise ! La propriété <code>length</code> d’un tableau est en <em>lecture/écriture</em>. On peut donc s’en servir pour <em>réutiliser un tableau existant</em> et sa mémoire allouée, comme ceci :</p>
  696. <figure class="code"><figcaption><span/></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class="line-number">1</span>
  697. <span class="line-number">2</span>
  698. <span class="line-number">3</span>
  699. <span class="line-number">4</span>
  700. </pre></td><td class="code"><pre><code class="javascript"><span class="line"><span class="c1">// travail sur un tableau "arr" qui devient maousse…</span>
  701. </span><span class="line">
  702. </span><span class="line"><span class="c1">// « remise à zéro » -- La classe</span>
  703. </span><span class="line"><span class="nx">arr</span><span class="p">.</span><span class="nx">length</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  704. </span></code></pre></td></tr></table></div></figure>
  705. <p>Là aussi, les perfs obtenues sont largement meilleures pour les cas où les tableaux grandissent significativement au fil du temps.</p>
  706. <h2>10. Convertir les nombres correctement</h2>
  707. <p>Figurez-vous que <code>parseInt</code> est un menteur doublé d’un salaud et que <code>parseFloat</code> est juste la version pourrie de <code>Number(…)</code>. Après cette phrase un brin raccoleuse (mais parfaitement véridique), je vous invite à consulter tous les détails dans l’article que je publiais fin décembre : <a href="/2012/12/26/convertir-un-nombre-en-texte-en-javascript/">Convertir un texte en nombre en JavaScript</a></p>
  708. <h2>Envie d’en savoir plus ?</h2>
  709. <p>Nos ateliers de formation JavaScript couvrent ce genre d’aspects explicitement ou à travers tous leurs codes, et beaucoup, beaucoup plus encore. Jetez-y un œil, en plus ils sont bien moins chers que la moyenne du marché !</p>
  710. <ul>
  711. <li><a href="/js-puissant/">JS Puissant</a> s’intéresse au langage lui-même, y compris pour des usages très avancés.</li>
  712. <li><a href="/js-guru/">JS Guru</a> explore l’écosystème front-end en détail et permet la réalisation de bout-en-bout d’une <em>Single-Page App</em> ultra-moderne mettant en œuvre de façon intégrée tout plein de technos « HTML5 » à l’aide d’un outillage développeur de premier plan.</li>
  713. <li><a href="/js-total/">JS Total</a> reprend les deux autres et rajoute les problématiques de tests automatisés, documentation, optimisations tous azimuts et web mobile.</li>
  714. </ul>