|
| 1 | +# Frontend Guidelines |
| 2 | + |
| 3 | +## JavaScript |
| 4 | + |
| 5 | +### Apply |
| 6 | + |
| 7 | +Forget about `apply()`. Use the spread operator instead. |
| 8 | + |
| 9 | +```javascript |
| 10 | +const greet = (first, last) => `Hi ${first} ${last}`; |
| 11 | +const person = ["John", "Doe"]; |
| 12 | + |
| 13 | +// bad |
| 14 | +greet.apply(null, person); |
| 15 | + |
| 16 | +// good |
| 17 | +greet(...person); |
| 18 | +``` |
| 19 | +### Arguments |
| 20 | + |
| 21 | +Forget about the `arguments` object. The rest parameter is always a better option because: |
| 22 | + |
| 23 | +1. it's named, so it gives you a better idea of the arguments the function is expecting |
| 24 | +2. it's a real array, which makes it easier to use. |
| 25 | + |
| 26 | +```javascript |
| 27 | +// bad |
| 28 | +const sortNumbers = () => |
| 29 | + Array.prototype.slice.call(arguments).sort(); |
| 30 | + |
| 31 | +// good |
| 32 | +const sortNumbers = (...numbers) => numbers.sort(); |
| 33 | +``` |
| 34 | +### Bind |
| 35 | + |
| 36 | +Don't `bind()` when there's a more idiomatic approach. |
| 37 | + |
| 38 | +```javascript |
| 39 | +// bad |
| 40 | +["foo", "bar"].forEach(func.bind(this)); |
| 41 | + |
| 42 | +// good |
| 43 | +["foo", "bar"].forEach(func, this); |
| 44 | +``` |
| 45 | +```javascript |
| 46 | +// bad |
| 47 | +const person = { |
| 48 | + first: "John", |
| 49 | + last: "Doe", |
| 50 | + greet() { |
| 51 | + const full = function() { |
| 52 | + return `${this.first} ${this.last}`; |
| 53 | + }.bind(this); |
| 54 | + return `Hello ${full()}`; |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +// good |
| 59 | +const person = { |
| 60 | + first: "John", |
| 61 | + last: "Doe", |
| 62 | + greet() { |
| 63 | + const full = () => `${this.first} ${this.last}`; |
| 64 | + return `Hello ${full()}`; |
| 65 | + } |
| 66 | +} |
| 67 | +``` |
| 68 | +### Caching |
| 69 | + |
| 70 | +Cache feature tests, large data structures and any expensive operation. |
| 71 | + |
| 72 | +```javascript |
| 73 | +// bad |
| 74 | +const contains = (arr, value) => |
| 75 | + Array.prototype.includes |
| 76 | + ? arr.includes(value) |
| 77 | + : arr.some(el => el === value); |
| 78 | +contains(["foo", "bar"], "baz"); // => false |
| 79 | + |
| 80 | +// good |
| 81 | +const contains = (() => |
| 82 | + Array.prototype.includes |
| 83 | + ? (arr, value) => arr.includes(value) |
| 84 | + : (arr, value) => arr.some(el => el === value) |
| 85 | +)(); |
| 86 | +contains(["foo", "bar"], "baz"); // => false |
| 87 | +``` |
| 88 | +### Higher-order functions |
| 89 | + |
| 90 | +Avoid nesting functions when you don't have to. |
| 91 | + |
| 92 | +```javascript |
| 93 | +// bad |
| 94 | +[1, 2, 3].map(num => String(num)); |
| 95 | + |
| 96 | +// good |
| 97 | +[1, 2, 3].map(String); |
| 98 | +``` |
| 99 | +### Coercion |
| 100 | + |
| 101 | +Embrace implicit coercion when it makes sense. Avoid it otherwise. Don't cargo-cult. |
| 102 | + |
| 103 | +```javascript |
| 104 | +// bad |
| 105 | +if (x === undefined || x === null) { ... } |
| 106 | + |
| 107 | +// good |
| 108 | +if (x == undefined) { ... } |
| 109 | +``` |
| 110 | +### Composition |
| 111 | + |
| 112 | +Avoid multiple nested function calls. Use composition instead. |
| 113 | + |
| 114 | +```javascript |
| 115 | +// bad |
| 116 | +const plus1 = a => a + 1; |
| 117 | +const mult2 = a => a * 2; |
| 118 | + |
| 119 | +mult2(plus1(5)); // => 12 |
| 120 | + |
| 121 | + |
| 122 | +// good |
| 123 | +const pipeline = (...funcs) => |
| 124 | + val => funcs.reduce((a, b) => b(a), val); |
| 125 | + |
| 126 | +const plus1 = a => a + 1; |
| 127 | +const mult2 = a => a * 2; |
| 128 | +const addThenMult = pipeline(plus1, mult2); |
| 129 | + |
| 130 | +addThenMult(5); // => 12 |
| 131 | +``` |
| 132 | +### Conditions |
| 133 | + |
| 134 | +Favor IIFE's and return statements over if, else if, else and switch statements. |
| 135 | + |
| 136 | +```javascript |
| 137 | +// bad |
| 138 | +var grade; |
| 139 | +if (result < 50) |
| 140 | + grade = "bad"; |
| 141 | +else if (result < 90) |
| 142 | + grade = "good"; |
| 143 | +else |
| 144 | + grade = "excellent"; |
| 145 | + |
| 146 | +// good |
| 147 | +const grade = (() => { |
| 148 | + if (result < 50) |
| 149 | + return "bad"; |
| 150 | + if (result < 90) |
| 151 | + return "good"; |
| 152 | + return "excellent"; |
| 153 | +})(); |
| 154 | +``` |
| 155 | +### Curry |
| 156 | + |
| 157 | +Currying might have its place in other languages, but avoid it in JavaScript. It makes your code harder to read by introducing a foreign paradigm while the appropriate use cases are extremely unusual. |
| 158 | + |
| 159 | +```javascript |
| 160 | +// bad |
| 161 | +const sum = a => b => a + b; |
| 162 | +sum(5)(3); // => 8 |
| 163 | + |
| 164 | +// good |
| 165 | +const sum = (a, b) => a + b; |
| 166 | +sum(5, 3); // => 8 |
| 167 | +``` |
| 168 | +### Dependencies |
| 169 | + |
| 170 | +Minimize dependencies. Third-party is code you don't know. Don't load an entire library for just a couple of methods easily replicable: |
| 171 | + |
| 172 | +```javascript |
| 173 | +// bad |
| 174 | +var _ = require("underscore"); |
| 175 | +_.compact(["foo", 0])); |
| 176 | +_.unique(["foo", "foo"]); |
| 177 | +_.union(["foo"], ["bar"], ["foo"]); |
| 178 | + |
| 179 | +// good |
| 180 | +const compact = arr => arr.filter(el => el); |
| 181 | +const unique = arr => [...Set(arr)]; |
| 182 | +const union = (...arr) => unique([].concat(...arr)); |
| 183 | + |
| 184 | +compact(["foo", 0]); |
| 185 | +unique(["foo", "foo"]); |
| 186 | +union(["foo"], ["bar"], ["foo"]); |
| 187 | +``` |
| 188 | +### Loops |
| 189 | + |
| 190 | +Don't use loops as they force you to use mutable objects. Rely on `array.prototype` methods. |
| 191 | + |
| 192 | +```javascript |
| 193 | +// bad |
| 194 | +const sum = arr => { |
| 195 | + var sum = 0; |
| 196 | + var i = -1; |
| 197 | + for (;arr[++i];) { |
| 198 | + sum += arr[i]; |
| 199 | + } |
| 200 | + return sum; |
| 201 | +}; |
| 202 | + |
| 203 | +sum([1, 2, 3]); // => 6 |
| 204 | + |
| 205 | +// good |
| 206 | +const sum = arr => |
| 207 | + arr.reduce((x, y) => x + y); |
| 208 | + |
| 209 | +sum([1, 2, 3]); // => 6 |
| 210 | +``` |
| 211 | +If you can't, or if using `array.prototype` methods is arguably abusive, use recursion. |
| 212 | +```javascript |
| 213 | +// bad |
| 214 | +const createDiv = howMany => { |
| 215 | + while (howMany--) { |
| 216 | + document.body.insertAdjacentHTML("beforeend", "<div></div>"); |
| 217 | + } |
| 218 | +}; |
| 219 | +createDiv(5); |
| 220 | + |
| 221 | +// bad |
| 222 | +const createDivs = howMany => |
| 223 | + [...Array(howMany)].forEach(() => |
| 224 | + document.body.insertAdjacentHTML("beforeend", "<div></div>") |
| 225 | + ); |
| 226 | +createDiv(5); |
| 227 | + |
| 228 | +// good |
| 229 | +const createDiv = howMany => { |
| 230 | + if (!howMany) return; |
| 231 | + document.body.insertAdjacentHTML("beforeend", "<div></div>"); |
| 232 | + return createDiv(howMany - 1); |
| 233 | +}; |
| 234 | +createDiv(5); |
| 235 | +``` |
| 236 | +### Natives |
| 237 | + |
| 238 | +Rely on native methods as much as possible. |
| 239 | + |
| 240 | +```javascript |
| 241 | +// bad |
| 242 | +const toArray = obj => [].slice.call(obj); |
| 243 | + |
| 244 | +// good |
| 245 | +const toArray = (() => |
| 246 | + Array.from ? Array.from : obj => [].slice.call(obj) |
| 247 | +)(); |
| 248 | +``` |
| 249 | +### Object iteration |
| 250 | + |
| 251 | +Avoid `for...in` when you can. |
| 252 | + |
| 253 | +```javascript |
| 254 | +const shared = { foo: "foo" }; |
| 255 | +const obj = Object.create(shared, { |
| 256 | + bar: { |
| 257 | + value: "bar", |
| 258 | + enumerable: true |
| 259 | + } |
| 260 | +}); |
| 261 | + |
| 262 | +// bad |
| 263 | +for (var prop in obj) { |
| 264 | + if (obj.hasOwnProperty(prop)) |
| 265 | + console.log(prop); |
| 266 | +} |
| 267 | + |
| 268 | +// good |
| 269 | +Object.keys(obj).forEach(prop => console.log(prop)); |
| 270 | +``` |
| 271 | +### Performance |
| 272 | + |
| 273 | +Favor readability, correctness and expressiveness over performance. JavaScript will basically never be your performance bottleneck. Optimize things like image compression, network access and DOM reflows instead. |
| 274 | + |
| 275 | +```javascript |
| 276 | +// bad (albeit way faster) |
| 277 | +const arr = [1, 2, 3, 4]; |
| 278 | +const len = arr.length; |
| 279 | +var i = -1; |
| 280 | +var result = []; |
| 281 | +while (++i < len) { |
| 282 | + var n = arr[i]; |
| 283 | + if (n % 2 > 0) continue; |
| 284 | + result.push(n * n); |
| 285 | +} |
| 286 | + |
| 287 | +// good |
| 288 | +const arr = [1, 2, 3, 4]; |
| 289 | +const isEven = n => n % 2 == 0; |
| 290 | +const square = n => n * n; |
| 291 | + |
| 292 | +const result = arr.filter(isEven).map(square); |
| 293 | +``` |
| 294 | +### Readability |
| 295 | + |
| 296 | +Don't obfuscate the intent of your code by using seemingly smart tricks. |
| 297 | + |
| 298 | +```javascript |
| 299 | +// bad |
| 300 | +foo || doSomething(); |
| 301 | + |
| 302 | +// good |
| 303 | +if (!foo) doSomething(); |
| 304 | +``` |
| 305 | +```javascript |
| 306 | +// bad |
| 307 | +void function() { /* IIFE */ }(); |
| 308 | + |
| 309 | +// good |
| 310 | +(function() { /* IIFE */ }()); |
| 311 | +``` |
| 312 | +```javascript |
| 313 | +// bad |
| 314 | +const n = ~~3.14; |
| 315 | + |
| 316 | +// good |
| 317 | +const n = Math.floor(3.14); |
| 318 | +``` |
| 319 | +### Code reuse |
| 320 | + |
| 321 | +Don't be afraid of creating lots of small, highly composable and reusable functions. |
| 322 | + |
| 323 | +```javascript |
| 324 | +// bad |
| 325 | +arr[arr.length - 1]; |
| 326 | + |
| 327 | +// good |
| 328 | +const first = arr => arr[0]; |
| 329 | +const last = arr => first(arr.slice(-1)); |
| 330 | +last(arr); |
| 331 | +``` |
| 332 | +```javascript |
| 333 | +// bad |
| 334 | +const product = (a, b) => a * b; |
| 335 | +const triple = n => n * 3; |
| 336 | + |
| 337 | +// good |
| 338 | +const product = (a, b) => a * b; |
| 339 | +const triple = product.bind(null, 3); |
| 340 | +``` |
| 341 | +### Statelessness |
| 342 | + |
| 343 | +Try to keep your functions pure. All functions should ideally produce no side-effects, use no outside data and return new objects instead of mutating existing ones. |
| 344 | + |
| 345 | +```javascript |
| 346 | +// bad |
| 347 | +const merge = (target, ...sources) => Object.assign(target, ...sources); |
| 348 | +merge({ foo: "foo" }, { bar: "bar" }); // => { foo: "foo", bar: "bar" } |
| 349 | + |
| 350 | +// good |
| 351 | +const merge = (...sources) => Object.assign({}, ...sources); |
| 352 | +merge({ foo: "foo" }, { bar: "bar" }); // => { foo: "foo", bar: "bar" } |
| 353 | +``` |
| 354 | +### Variables |
| 355 | + |
| 356 | +Favor `const` over `let` and `let` over `var`. |
| 357 | + |
| 358 | +```javascript |
| 359 | +// bad |
| 360 | +var obj = {}; |
| 361 | +obj["foo" + "bar"] = "baz"; |
| 362 | + |
| 363 | +// good |
| 364 | +const obj = { |
| 365 | + ["foo" + "bar"]: "baz" |
| 366 | +}; |
| 367 | +``` |
0 commit comments