Javascript - Best Practices

javascript

This is my collection of best practices for JavaScript. This may or may not reflect whether I agree with these best practices. I just list them here for my sake. Whether you agree with these best practices or not, it is up to you. Use them if they fit your styles and believes. You should know the consequence though.

Why should we always comment your code?

Comments should be used liberally to improve the readability of your programs. Take care that the comments always accurately describe the code. Obsolete comments are worse than no comments.

Why should we avoid the block comment form?

JavaScript offers two forms of comments, block comments formed with /* */ and line-ending comments starting with //.

The /* */ forms a block comment. In JavaScript, those pairs can also occur in regular expression literals, so block comments are not safe for commenting out blocks of code. For example:

/*
    var rm_a = /a*/.match(s);
*/

causes a syntax error. Error such as this can be hard to find, especially if you are not aware of it, or forgot about it. Therefore, it is recommended that we use // instead of /* */

Why should we remeber the reserved words and avoid using reserved words even as name of a property in a JSON object?

A name (identifier) is a letter optionally followed by one or more letters, digits, or underbars. A name cannot be one of these reserved words: abstract, boolean, break, byte, case, catch, char, class, const, continue, debugger, default, delete, do, double, else, enum, export, extends, false, final, finally, float, for, function, goto, if, implements, import, in, instanceof, init, interface, long, native, new, null, package, private, protected, public, return, short, static, super, switch, synchronized, this, throw, throws, transient, true, try, typeof, var, volatile, void, while, with

It is not permitted to use a reserved word as the name of an object property in an object literal or following a dot.

Why should we always declare variables using the var keyword?

When used inside a function, the var statement defines the function's private variables. If we forget to use the var keyword, we end up defining the variable in global space. Defining variable in global space is bad because one function can overwrite a variable defined by another function, if these two function happen to use two variables with the same name. When this happens, the result can be unpredictable and is very hard to fix;

Why should we use namespace?

var MYAPP = {};
MYAPP.stooge = {
    "first-name": "Joe",
    "last-name": "Howard"
};

By reducing your global footprint to a single name, you significantly reduce the chance of bad interactions with other applications, widgets, or libraries. Your program also becomes easier to read because it is obvious that MYAPP.stooge refers to a top-level structure.

Why should we declare variables at the top?

In many modern languages, it is recommended that variables be declared as late as possible, at the first point of use. That turns out to be a bad advice for JavaScript because it lacks block scope. So instead, it is best to declare all of the variables used in a function at the top of the function body.

Why should we give Users Smart Warnings?

Once you've determined that your user has made a mistake, let them know as soon and as clearly as possible. Put your error message close to the bad field, and let your user know what is wrong with the entry. The onblur handler may be used for this purpose (as soon as the user hop to the next box, the previous one is validated)

Should we use AMD or browserify?

These days I had to implement a build script, on a project we are launching in the very near future. The JavaScript is written with AMD (RequireJS), which is very good for development, since there is no compile step, no source-maps, etc. Yet for production builds I found r.js is not the best tool around. Instead, I went for browserify, which does a great job when bundling multiple js files. The problem was the AMD - CommonJS difference in style. So I set out to write a browserify transform plugin. I think AMD is easier to work with than browserify in the development stage, since you have no additional build step. But as for bundling files to production, browserify is king, so unAMD helps you get the best of both world. See http://jslog.com/2014/06/25/convert-amd-to-commonjs/

What is a blocking script?

Before the browser can render a page it has to build the DOM tree by parsing the HTML markup. During this process, whenever the parser encounters a script it has to stop and execute it before it can continue parsing the HTML. In the case of an external script the parser is also forced to wait for the resource to download, which may incur one or more network roundtrips and delay the time to first render of the page.

You should avoid and minimize the use of blocking JavaScript, especially external scripts that must be fetched before they can be executed. Scripts that are necessary to render page content can be inlined to avoid extra network requests, however the inlined content needs to be small and must execute quickly to deliver good performance. Scripts that are not critical to initial render should be made asynchronous or deferred until after the first render.

Please keep in mind that for this to improve your loading time, you must also optimize CSS delivery.

External blocking scripts force the browser to wait for the JavaScript to be fetched, which may add one or more network roundtrips before the page can be rendered. If the external scripts are small, you can inline their contents directly into the HTML document and avoid the network request latency.

Inlining the script contents eliminates the external request for small.js and allows the browser to deliver a faster time to first render. However, note that inlining also increases the size of the HTML document and that the same script contents may need to be inlined across multiple pages. As a result, you should only inline small scripts to deliver best performance.

By default JavaScript blocks DOM construction and thus delays the time to first render. To prevent JavaScript from blocking the parser we recommend using the HTML async attribute on external scripts. For example:

<script async src="my.js">

Note that asynchronous scripts are not guaranteed to execute in specified order and should not use document.write. Scripts that depend on execution order or need to access or modify the DOM or CSSOM of the page may need to be rewritten to account for these constraints.

What is defer loading of JavaScript?

The loading and execution of scripts that are not necessary for the initial page render may be deferred until after the initial render or other critical parts of the page have finished loading. Doing so can help reduce resource contention and improve performance.

Should I use client-side rendering or should use server-side rendering?

If the content of the page is constructed by client-side JavaScript, then you should investigate inlining the relevant JavaScript modules to avoid extra network roundtrips. Similarly, leveraging server-side rendering can significantly improve first page load performance: render JavaScript templates on the server to deliver fast first render, and then use client-side templating once the page is loaded. For more information on server-side rendering, see http://youtu.be/VKTWdaupft0?t=14m28s.

What is the meaning of the defer attribute of the script tag?

A script that will not run until after the page has loaded:

 <script src="demo_defer.js" defer></script>

The defer attribute is a boolean attribute. When present, it specifies that the script is executed when the page has finished parsing. The defer attribute is only for external scripts (should only be used if the src attribute is present).

What is the meaning of the async attribute of the script tag?

There are several ways an external script can be executed:

  • If async is present: The script is executed asynchronously with the rest of the page (the script will be executed while the page continues the parsing)
  • If async is not present and defer is present: The script is executed when the page has finished parsing
  • If neither async or defer is present: The script is fetched and executed immediately, before the browser continues parsing the page
  • http://stackoverflow.com/questions/5250412/how-exactly-does-script-defer-defer-work

Can old browsers download scripts in parallel?

No. Older browsers would actually stop doing everything, including downloading additional resources in the page, while a script was downloading. That meant two <script> tags in a row would result in the browser waiting to begin download of the second script until after the first was downloaded and executed.

Can modern browsers download scripts in parallel?

Yes. Newer browsers will download the script files in parallel and then execute them in order, so the second script is ready to be executed as soon as the first complete (for more information, read Steve’s post on this).

Can modern browsers download and execute scripts in parallel?

No. Newer browsers will download the script files in parallel and then execute them in order, so the second script is ready to be executed as soon as the first complete (for more information, read Steve’s post on this).

What is parallel downloading and what is asynchronous execution?

Parallel downloading should not be confused for asynchronous execution. Remember, JavaScript is single threaded, so you literally cannot be executing two scripts at the same time. Parallel downloading of scripts only means that two scripts are downloaded at the same time, not that they’re executed at the same time. There’s a big difference.

How can we load a script in a non-blocking manner?

  1. Create a script node dynamically after the initial page is rendered.
  2. Use the async attribute. HTML5 introduces a new attribute on the <script> tag called async. This is a Boolean attribute (doesn’t require a value) and, when specified, causes the script file to be loaded as if you had created a dynamic script node.

How do modern browsers behave when the async attribute is set on two script tags?

Newer browsers will download the script files in parallel and then execute them in order, so the second script is ready to be executed as soon as the first complete.

How do modern browsers behave when the async attribute is set on two dynamic script tags?

When set on a dynamic script node, the behavior of the async attribute has a subtle distinction. Test this yourself and see if removing the async attribute from the dynamic script tags make any difference.

How can we prevent existing properties from being overwritten or deleted?

You can prevent existing properties from being overwritten (setting writable to false) or deleted (setting configurable to false).

How can we prevent new properties from being added to an object?

You can prevent objects from being assigned new properties (using Object.preventExtensions()) or set all properties to be read-only and not deletable (Object.freeze()).

If you don’t want all the properties to read-only, then you can use Object.seal(). This prevents new properties from being added and existing properties from being removed but otherwise allows properties to behave normally.

"use strict";

var person = {
    name: "Nicholas"
};
Object.seal(person);
person.age = 20;    // Error!

What are the differences between Object.freeze() and Object.seal()?

If you don’t want all the properties to read-only, then you can use Object.seal(). This prevents new properties from being added and existing properties from being removed but otherwise allows properties to behave normally.

What happens if we try to add a new property to a sealed object while using strict mode?

The browser will throw an error. A sealed object, when used in strict mode, will throw an error when you try to add a new property.

How can we create an object in such a way that JavaScript will throws an error rather than returning undefined when you try to access a property that does not exist?

Proxies have a long and complicated history in ECMAScript 6. An early proposal was implemented by both Firefox and Chrome before TC-39 decided to change proxies in a very dramatic way. The changes were, in my opinion, for the better, as they smoothed out a lot of the rough edges from the original proxies proposal.

The biggest change was the introduction of a target object with which the proxy would interact. Instead of just defining traps for particular types of operations, the new “direct” proxies intercept operations intended for the target object. They do this through a series of methods that correspond to under-cover-operations in ECMAScript. For instance, whenever you read a value from an object property, there is an operation called [[Get]] that the JavaScript engine performs. The [[Get]] operation has built-in behavior that can’t be changed, however, proxies allow you to “trap” the call to [[Get]] and perform your own behavior. Consider the following:

var proxy = new Proxy({ name: "Nicholas" }, {
    get: function(target, property) {
        if (property in target) {
            return target[property];
        } else {
            return 35;
        }
    }
});

console.log(proxy.time);        // 35
console.log(proxy.name);        // "Nicholas"
console.log(proxy.title);       // 35

This proxy uses a new object as its target (the first argument to Proxy()). The second argument is an object that defines the traps you want. The get method corresponds to the [[Get]] operation (all other operations behave as normal so long as they are not trapped). The trap receives the target object as the first argument and the property name as the second. This code checks to see if the property exists on the target object and returns the appropriate value. If the property doesn’t exist on the target, the function intentionally ignores the two arguments and always returns 35. So no matter which non-existent property is accessed, the value 35 is always returned.

function createDefensiveObject(target) {

    return new Proxy(target, {
        get: function(target, property) {
            if (property in target) {
                return target[property];
            } else {
                throw new ReferenceError("Property \"" + property + "\" does not exist.");
            }
        }
    });
}

What is a defensive object?

A defensive object would throw an error if you try to access a property that does not exist.

Can we add new property to a defensive object?

Yes. Unless we froze or sealed the object.

How can we create a defensive object?

function createDefensiveObject(target) {

    return new Proxy(target, {
        get: function(target, property) {
            if (property in target) {
                return target[property];
            } else {
                throw new ReferenceError("Property \"" + property + "\" does not exist.");
            }
        }
    });
}

If we have a defensive object, how can we check if a property exist without triggering the exception?

Use hasOwnProperty:

var person = {
    name: "Nicholas"
};

var defensivePerson = createDefensiveObject(person);

console.log("name" in defensivePerson);               // true
console.log(defensivePerson.hasOwnProperty("name"));  // true

console.log("age" in defensivePerson);                // false
console.log(defensivePerson.hasOwnProperty("age"));   // false

How can we truly defend the interface of an object?

You can then truly defend the interface of an object, disallowing additions and erroring when accessing a non-existent property, by using a couple of steps:

var person = {
    name: "Nicholas"
};

Object.preventExtensions(person);

var defensivePerson = createDefensiveObject(person);

defensivePerson.age = 13;                 // Error!
console.log(defensivePerson.age);         // Error!

When is the most useful time to create a defensive object?

Perhaps the most useful time to use defensive objects is when defining a constructor, as this typically indicates that you have a clearly-defined contract that you want to preserve. For example:

function Person(name) {
    this.name = name;

    return createDefensiveObject(this);
}

var person = new Person("Nicholas");

console.log(person.age);         // Error!

By calling createDefensiveObject() inside of a constructor, you can effectively ensure that all instances of Person are defensive.

How can we use ECMAScript 6 Proxy to create type-safe objects?

var person = {
    name: "Nicholas",
    age: 16
};

In this code, it’s easy to see that name should hold a string and age should hold a number. You wouldn’t expect these properties to hold other types of data for as long as the object is used. Using proxies, it’s possible to use this information to ensure that new values assigned to these properties are of the same type.

Since assignment is the operation to worry about (that is, assigning a new value to a property), you need to use the proxy set trap. The set trap gets called whenever a property value is set and receives four arguments: the target of the operation, the property name, the new value, and the receiver object. The target and the receiver are always the same (as best I can tell). In order to protect properties from having incorrect values, simply evaluate the current value against the new value and throw an error if they don’t match:

function createTypeSafeObject(object) {

    return new Proxy(object, {
          set: function(target, property, value) {
              var currentType = typeof target[property],
                  newType = typeof value;

              if (property in target && currentType !== newType) {
                  throw new Error("Property " + property + " must be a " + currentType + ".");
              } else {
                  target[property] = value;
              }
          }
    });
}

The createTypeSafeObject() method accepts an object and creates a proxy for it with a set trap. The trap uses typeof to get the type of the existing property and the value that was passed in. If the property already exists on the object and the types don’t match, then an error is thrown. If the property either doesn’t exist already or the types match, then the assignment happens as usual. This has the effect of allowing objects to receive new properties without error. For example:

var person = {
    name: "Nicholas"
};

var typeSafePerson = createTypeSafeObject(person);

typeSafePerson.name = "Mike";        // succeeds, same type
typeSafePerson.age = 13;             // succeeds, new property
typeSafePerson.age = "red";          // throws an error, different types

In this code, the name property is changed without error because it’s changed to another string. The age property is added as a number, and when the value is set to a string, an error is thrown. As long as the property is initialized to the proper type the first time, all subsequent changes will be correct. That means you need to initialize invalid values correctly. The quirk of typeof null returning “object” actually works well in this case, as a null property allows assignment of an object value later.

When is the most useful time to create type-safe object?

As with defensive objects, you can also apply this method to constructors:

function Person(name) {
    this.name = name;
    return createTypeSafeObject(this);
}

var person = new Person("Nicholas");

console.log(person instanceof Person);    // true
console.log(person.name);                 // "Nicholas"

Since proxies are transparent, the returned object has all of the same observable characteristics as a regular instance of Person, allowing you to create as many instances of a type-safe object while making the call to createTypeSafeObject() only once.

What is tail recursion?

ECMAScript 6 will have tail call optimization: If a function call is the last action in a function, it is handled via a “jump”, not via a “subroutine call”.

How can we rewrite our recursive method to take advantage of tail recursion optimization?

The original version:

function computeMaxCallStackSize() {
    return 1 + computeMaxCallStackSize();
}

The modified version:

function computeMaxCallStackSize(size) {
    size = size || 1;
    return computeMaxCallStackSize(size + 1);
}

This code was taken from http://www.2ality.com/2014/04/call-stack-size.html and modified a bit. I am not sure if the orginal version is tailed optimized or not. We should try it out.

The modified version should run forever under ECMAScript 6 (in strict mode). Does this means if the browser support ECMAScript 6, the modified version would run indefinitely without the browser display the "slow script" error?

What is the purpose of Object.observe()?

Two-way data binding is now one of the crucial features of client-side applications. Without data binding, a developer has to deal with a lot of logic to manually bind data to the view, whenever there is a change in the model. JavaScript libraries like Knockout, AngularJS, and Ember have support for two-way binding but these libraries use different techniques to to detect changes.

Knockout and Ember use observables. Observables are functions wrapped around the properties of the model objects. These functions are invoked whenever there is a change of the value of the corresponding object or property. Although this approach works well, and detects and notifies all the changes, it takes away the freedom of working with plain JavaScript objects as now we have to deal with functions.

Angular uses dirty checking to detect changes. This approach doesn’t pollute the model object. It registers watchers for every object added to the model. All of these watchers are executed whenever Angular’s digest cycle kicks in and if there are any changes to the data. Those changes are processed by the corresponding watchers. The model still remains a plain object, as no wrappers are created around it. But, this technique causes performance degradation as the number of watchers grows.

Object.observe() is a feature to be added to JavaScript as part of ECMAScript 7 to support object change detection natively in the browser.

Because Object.observe will be supported by the browsers natively and it works directly on the object without creating any wrappers around it, the API is both easy to use and a win for performance. If Object.observe is supported by a browser, you can implement two-way binding without the need of an external library. It doesn’t mean that all of the existing two-way binding libraries will be of no use once Object.observe is implemented. We still need them to update UIs efficiently after detecting the changes using Object.observe. Besides, libraries would internally polyfill the logic of change detection if not all targeted browsers support Object.observe.

So Object.observe is the native way for code to be notified when an object is modified.

How can we use Object.observe?

The observe() method is an asynchronous static method defined on Object. It can be used to look for changes of an object and it accepts three parameters:

  • an object to be observed
  • a callback function to be called when a change is detected
  • an optional array containing types of changes to be watched for
var person = {
  name: 'Ravi',
  country: 'India',
  gender: 'Male'
};

function observeCallback(changes){
  console.log(changes);
};

Object.observe(person, observeCallback);

person.name = 'Rama';  // Updating value
person.occupation = 'writer';  // Adding a new property
delete person.gender;  // Deleting a property

In the above code we created an object literal with some data. We also defined a function named observeCallback() that we’ll use to log the changes of the object. Then, we start observing for changes by using Object.observe. Finally, we performed some changes on the object.

Object.observe is asynchronous, and therefore does not notify change to the object for each change. In the above code, our observeCallback function receives one parameters changes. This parameter is an array of changes.

Object.observe runs asynchronously and it groups all of the changes happened and passes them to the callback when it’s called. So, here we received three entries for the three changes applied on the object. As you see, each entry consists of name of the property changed, the old value, the type of the change, and the object itself with the new values.

How can we specify the type of changes that we want to be notified with Object.observe?

In our code we didn’t specify the types of changes to look for, so it observes additions, updates and deletes. This can be controlled using the third parameter of the observe method as follows:

Object.observe(person, observeCallback, ['add', 'update']);

What are the limitation with Object.observe?

The observe() method is capable of detecting changes made on direct properties added to an object. It cannot detect changes on properties created using getters and setters. Because the behavior of these properties is controlled by the author, changes detection also have to be owned by the author. To address this issue, we need to use a notifier (available through Object.getNotifier()) to notify the changes made on the property. Consider the following snippet:

function TodoType() {
  this.item = '';
  this.maxTime = '';

  var blocked = false;

  Object.defineProperty(this, 'blocked', {
    get:function(){
      return blocked;
    },
    set: function(value){
      Object.getNotifier(this).notify({
        type: 'update',
        name: 'blocked',
        oldValue: blocked
      });
      blocked = value;
    }
  });
}

var todo = new TodoType();

todo.item = 'Get milk';
todo.maxTime = '1PM';

console.log(todo.blocked);

Object.observe(todo, function(changes){
  console.log(changes);
}, ['add', 'update']);

todo.item = 'Go to office';
todo.blocked = true;

TodoType is a constructor function with two properties. In addition to them, blocked is added using Object.defineProperty. In our example the setter defined for this property is a simple one. In a typical business application, it may perform some validations and it may not set value in case the validation fails. However, I wanted to keep things simple. As a last note, you can see that in our example the notification is sent only when there is an update.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License