Related: Angular nuggets
Remove comments
A well-named function replaces comments
1 | authService.checkLogin(null, null, function() { |
Use comments in your json files (really)
This is valid JSON syntax
1 | { |
Refactor conditions
1 | if ($scope.selectedProduct && |
Avoid exceptions using short circuit expressions
If we have a complex condition, for example if an argument is an object
and has a certain property, the condition can cause an exception if
an object is undefined
.
1 | function isValid(person) { |
JavaScript uses short circuit evaluation for expressions in the conditions
to avoid evaluating expressions if the entire value is known, which is often
the case with Boolean expressions. In the above code, only the left hand
of the logical AND is evaluated. Since it is false (there is no person
)
the condition returns without evaluating person.name
.
This often fails if we change the condition to OR. Then all parts might be evaluated, until at least one is true.
1 | function isValid(person) { |
To properly divide the condition we need to make the entire OR into its own expression. Then it will be evaluated only if the left hand of the AND condition passes.
1 | function isValid(person) { |
In the above code the value of (person.name || person.age)
will not be
computed if the person
is false because the short circuit principle stops
it.
Cast result to Boolean
Avoid undefined
values by casting the falsy values to real Booleans.
This will help when converting objects to JSON for example.
1 | function isValid(person) { |
Notice the empty object - the JSON.stringify
does NOT output properties
with undefined
values by default. You can use either a custom serializer
function or ensure that the function isValid
returns a true boolean.
1 | function bool(fn) { |
The output now has the desired property.
Name functions consistently
Helps with debugging the stack trace
1 | issues.config(function config() {}); |
Visual code patterns
1 | $scope.archive = function (issue) { |
Avoid required default arguments
1 | authService.checkLogin(null, null, onLoginError); |
Adjust checkLogin if necessary
1 | function checkLogin(username, onSuccess, onError) { |
Use more Jasmine matchers
The Jasmine testing framework comes with a few matchers expect(foo).toBeDefined()
, etc.
Jasmine-Matchers adds a LOT more, making
your unit tests a lot more descriptive.
Focus / skip single suite or test
See my post on how to run / skip individual suite or test.
Use console.debug
There is grunt-remove-logging
that removes console.debug
or other logging calls after concatenation, but before
minification step.
1 | console.log('something really, really important'); |
In general, if I see log messages in the DevTools on websites, I think lame.
Use inspect
to find the function
If you have a function definition (for example from Angular1 scope object) you can quickly jump to its definition in Chrome DevTools
1 | inspect($($0).scope().saveDescription) |
Use console.time
For simple timing in Node and browsers use
1 | const label = 'foo' |
Array concatenation
JavaScript flattens items during Array concatenation
1 | [].concat('a', 'b') |
Concatenating states
Use simple objects for product states, no need to have arrays with single object
1 | .config(function (productStates, anotherProductStates) { |
Exclude files from build
When using grunt to build the front-end, you can easily skip files using ! syntax
1 | // grabs only .js files, but not .spec.js |
Know your code's complexity
Use grunt-complexity plugin to measure source complexity. You can even fail the build if complexity is higher than a set limit.
$ grunt complexity
Running "complexity:all" (complexity) task
✓ Gruntfile.js ████ 88.600
✓ build.config.js ████ 98.062
✓ src/app/footer/footer.js ███████ 140.54
✓ src/app/footer/footer.spec.js ███████ 137.06
✓ src/app/header/header.js ██████ 127.84
✓ src/app/header/header.spec.js █████ 119.55
// higher number = simpler code
Simplify your Gruntfile.js
Gruntfiles grow in size and complexity with time. Luckily they can be easily refactored to bring them under control. A simple method is to move parts of config for different plugins into separate json files
1 | // Gruntfile.js |
1 | // build.complexity.json |
Each plugin an loads its configuration from a separate simple file.
Optional logging without extra code
I often turn on optional logging depending on the boolean flag.
1 | function foo(verbose) { |
These small if
statements increase Cyclomatic complexity,
leading to code that is harder to read and test. The following code is simpler:
1 | function foo(verbose) { |
Append array to existing array
We can easily concatenate JavaScript arrays, but this returns a new array
1 | var a = ['foo']; |
Sometimes we want to append second array to the existing one.
JavaScript Array.prototype.push
method can take multiple items to append. You can
pass the entire second array via apply
call
1 | var a = ['foo']; |
Remove element from array
Lodash has two useful methods for removing items from an array.
Both return new array. _.without
removes items by value, and _.remove
removes items
by callback.
1 | var a = ['foo', 'bar']; |
_.remove
is more versatile and can be used to remove items by property value
1 | var a = [{ foo: 2 }, { foo: 3 }]; |
Think of _.remove
as inverse of Array.prototype.filter
Quickly evaluate properties on the object
If you have nested property names as strings and an object, you can quickly evaluate them without prepending object name. For example, all the console log statements below print "baz".
1 | var foo = { |
This could be useful to evaluate expressions against a model object
1 | function $update(model, expression) { |
While there are very serious downsides
to with
, there are some neat tricks where it can be used, like tiny spreadsheet
in vanilla js.
Deep clone an object
To quickly deep clone an object, you can use this command JSON.parse(JSON.stringify(foo));
Ajax files from files
It is a pain to run a web server locally just so you can Ajax $.get(path)
. Chrome browser allows
you to enable files to make ajax requests to other local files. Just close the browser, then open
it with command line switch --allow-file-access-from-files
. For example
alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"
alias chromef="chrome --allow-file-access-from-files"
chromef index.html
Inside index.html
you can fetch files!
1 | $.get('foo.txt').done(function (data) { |
The full list of Chrome command line switches is available here.
Avoid boilerplate when calling methods
In nodejs I often use path.join
to form full paths. You can avoid always using path
object -
the methjod .join
does not use this
keyword (see Useful module pattern),
thus we can use it directly. We can also remove passing __dirname
every time using partial application
1 | // boilerplate |
Wrap streams into promises
The most efficient way in Node to copy / send a file is to pipe a read stream into a write stream and let the system process data in chunks.
1 | const fs = require('fs') |
Often we can just abstract the above operation into a promise-returning function for convenience.
1 | function letItFlow(read, write) { |
If you run the above examples, you will notice a curious thing. The Node process exits several seconds after printing "all done" message. We have a bug in the code - we think the operation is finished after the read stream emits "end" event. But the write stream has not finished yet. The correct solution would wait for write stream's finish event.
1 | function letItFlow(read, write) { |
The above promise resolves only when the write operation has been finished.