Skip to content

JavaScript

BryceCTaylor edited this page Dec 21, 2016 · 41 revisions

JavaScript

File Structure

...
 |-- js
      |-- vendors
      |-- data
      |-- [namespace]
...
  • vendors/ - for third-party JS files
  • data/ - for JSON files
  • [namespace]/ - e.g. UM, UDR, etc. for custom, project-based code
  • Don't add hand-written coded files at the js root folder
  • Categorize into sub-folders as needed
    • e.g. plugins/, controls/, pages/ etc.
    • Match code namespacing to folder structure

Indentation

Use tabs (not spaces). Tabsize should be equivalent to 4 spaces.

  • Why? For company-wide consistency, to reduce merge issues, and to improve readability and clear indentation

Brackets and Formatting

  • Starting bracket always on the same line
  • Why? Avoids issues with automatic semicolon insertion
  • Note: see Kernighan and Ritchie style C brace syntax
function funcName(param1) {
   ...
}

if (condition) {
    ...
} else {
    ...
}
  • Use semicolons: do not rely on automatic semicolon insertion
  • Use line spaces and indentation to maintain code readability
  • Quotes
  • Use single quotes rather than double quotes
  • Exception: unless escaping single quotes is necessary within a string

Linting

"Linting" refers to using a tool to programmatically check JavaScript for errors and ensure that code is well-formed and follows best practices.

  • jsLint - can be very "opinionated" about best practices
  • jsHint - more options if the "rules" for jsLint are too restrictive

See Build Tools for automated linting options.

Script Structure

Strict Mode

Use strict mode for all JavaScript by adding 'use strict'; as the top line inside each module. For more information, see this post by John Resig which describes strict mode.

  • Why? Strict mode helps out in a couple ways:
  • It catches some common coding mistakes, throwing exceptions. (i.e. forgetting to declare a variable with var)
  • It prevents, or throws errors, when relatively "unsafe" actions are taken (such as gaining access to the global object).
  • It disables JavaScript features that are confusing or poorly thought out.
  • The syntax is compatible with all browsers whether they support strict mode or not, so there is no reason not to include it.

Defining Variables

Define all variables at the top of a function.

  • Use comments to separate variables into groups and describe the purpose if necessary
  • Allows for easy scanning of a function's variables, and consistency between functions

When declaring multiple variables, always use keyword var for each variable.

  • Why? This is cleaner than multiple variable declarations and helps to avoid syntax errors when adding/removing variables.
var myVar1 = '1';
var myVar2 = '2';

Conditional Logic

For complex branching logic, prefer to use a hash map (object with key/value pairs) for value lookups over a switch statement or long if/else if/else blocks.

Function Definition

Prefer the use of named function declarations over assigning anonymous functions to variables.

  • Avoid: var myVar = function(){}
  • Use: function myFunc(){}

Using anonymous functions

The use cases for using anonymous functions instead of creating a named function include:

  • Creation of new scope blocks (See IIFEs)
  • To pass variables from an event handler to callback function, e.g.:
var anotherVar = 'A new var';
$('selector').on('click', function(e) { 
    functionName(e, anotherVar); 
});

Callback Functions

Use callback functions to pass functions as arguments to other functions, often with the expectation that the callback will execute asynchronously after something happens. Like with function declarations, prefer separate, named functions rather than nesting anonymous functions.

  • Avoid: $('selector').on('click', function(e) { functionName(e); });
  • Use: $('selector').on('click', functionName);

Eval

There are very few reasons to ever use eval. The preference should always be to use built-in browser functions or a library to deserialize JSON strings.

  • JSON.parse(jsonString);
  • $.parseJSON(jsonString);

Comments

Use // for comments.

  • Why? If the characters // are consistently used for authoring comments, then the combination /*...*/ can be used to comment out entire sections of code during development and debugging phases, because JavaScript does not allow comments to be nested using /*...*/.

An exception to this recommendation would be if a project is using JSDoc comments to document functions. JSDoc comments can be parsed by IDEs like IntelliJ IDEA (including WebStorm/PHPStorm) and NetBeans to provide code completion and other real-time coding assistance. JSDoc blocks start with /** to indicate to a parsing engine it is a JSDoc comment, and not a standard comment.

Sample JSDoc comment:

/**
 * Represents a book.
 * @constructor
 * @param {string} title - The title of the book.
 * @param {string} author - The author of the book.
 */
function Book(title, author) {
}

IIFE

Use an IIFE (Immediately Invoked Function Expression) to encapsulate logic or when you need to create a new scoping block. IIFEs are anonymous function expressions that execute immediately without polluting the current scope.

  • Always use the wrapping set of parens, even when not specifically necessary, as a matter of convention that indicates to other developers that the function is an IIFE and will be immediately invoked
(function() {
   // variables defined here are not available in the outer scope
}());

Revealing Module Pattern

Use the Revealing Module Pattern to structure JavaScript files. This pattern:

  • encapsulates code to prevent variables and functions from leaking into the global scope
  • allows for private variables that are usable inside the module, but are protected from the outside world
  • allows the syntax of our scripts to be more consistent.
  • makes it more clear at the end of the module which of our functions and variables may be accessed publicly which eases readability

Some keys when using the Revealing Module Pattern

  • Assign the returned module to the app namespace to keep all application variables name-spaced to a single exposed app variable. (App variable should be appropriately named based on the project.)
  • Define a single module per file.
  • Use underscores for private variables that don't get exposed
  • var _myPrivateVar; vs. var myPublicVar;

Example of the Revealing Module Pattern:

// Assign the module to a namespace variable.
app.moduleA = (function() {
    var _privateVar;
    var publicVar;

    function init() {
       // if the module needs initialization, use an init function 
       // so the entry point for modules is consistent
    }

    // use named function declarations rather than assigning anonymous functions to variables
    function foo() {}
    function _bar() {}

    return {
        // expose the elements that should be publicly accessible for the module,
        // aka, the module's API
        init: init, 
        publicVar: publicVar,
        foo: foo
    };
}());

To use exposed methods (i.e. in a different module):

app.moduleA.foo();

Initialization of modules

If all scripts have been concatenated (see Build Tools) initialize modules as needed by page or template with a script block before the closing </body> tag.

<script>
    app.moduleA.init();
</script>

Closures

Functions created inside the scope of the module will form closures over the elements in that module.

Definition: "A closure is created when a function is returned from another function, retaining its original scope."

  • Closures are created in the Module Pattern by virtue of the functions that are referenced in the returned object.
  • Functions that are returned from another function while referencing variable(s) defined in the first function's scope create a closure and retain the value of those variables, even if the returned function is called from a different scope.
  • Variables referenced in the closure cannot be garbage collected when the first function is complete, and could create a memory leak.

Closure example:

var foo = (function() {
    var _bar = 'baz';
    function getBar() { return _bar; }
    return {
        getBar: getBar
    };
}());

foo.getBar(); // returns the value of _bar, which is 'baz'
console.log(_bar); // 'undefined', _bar is out of scope

jQuery

  • If starting a brand new project, begin with the latest available final version of jQuery
  • Keep in mind 2.0+ will not work with IE6, 7, or 8 (old IE)
  • 1.x is feature compatible with 2.x but retains support for old IE
  • In 3.0, if you need to support IE8 and below, use jQuery Compat 3.0
  • Chain functions when possible
  • Why? This eliminates the lookup
  • $('selector').functionName().functionName();
  • Start each chained function in a jQuery chain on a new line indented if longer than two functions
$('selector')
    .functionName()
    .functionName()
    .functionName();
  • Choose optimal selectors
  • jQuery Sizzle (selector engine) is extremely fast, so don't pre-optimize your selectors
  • Choose to use concise and readable selectors
    • Avoid: .container .header ul li a
    • Use: .header a
  • Cache the lookup result in a variable if it will be reused
  • var $element = $('selector');
  • Prefix jQuery object variables with $
    • Why? Immediately obvious that the variable represents a jQuery object
  • Prefer the use of a for loop rather than .each() when iterating over more than a few items
  • Why? Native for loops are faster than jQuery .each
  • Use object notation for calls that can take multiple rules
  • Avoid: $('selector').css('color', '#c00');
  • Use: $('selector').css({'color': '#c00'});
    • Why? Allows additional properties to be added without modifying the structure

JS Frameworks

AngularJS

We have internal expertise and preferences for AngularJS, making it our recommended JavaScript framework when one is required.

See reStart Angular by Kim Maida for a sample starter project using best practices.

For style and standards questions for Angular, see the John Papa Styleguide, endorsed by the Angular team:

Additional Angular resources:

Clone this wiki locally