JS best practices

Getting into it

Firenze - Aperion - 26/02/2016

Maurizio Manetti

JS best practices part 2

  • Avoid globals with the module pattern
  • "use strict";
  • Tips & tricks
    • literal vs native
    • variable declaration
    • prototype vs constructor
    • useful shorthands
  • jQuery performances
  • The Console
  • WTFJS?
  • What's next?

AVOID GLOBALS

Why globals are bad?

Avoiding globals in JS: step 1/3

Imagine you have some vars and functions

var current = null;
function init(){...}
function change(){...}
function verify(){...}

First step: protect in a namespace

var myNameSpace = {
  current:null,
  init:function(){...},
  change:function(){...},
  verify:function(){...}
}

Drawback: to call the functions or change the variable value
you’ll always need to go via the name of the main object

Avoiding globals: step 2/3

Second step: protect the scope in an anonymous function (aka "the Module Pattern")

var myNameSpace = function(){
  var current = null;
  function init(){...}
  function change(){...}
  function verify(){...}
}();

Drawback: functions and vars are now
not accessible from outside the scope

Better: expose "public" methods (and/or properties)

var myNameSpace = function(){
  var current = null;
  function verify(){...}
  return{
    init:function(){...}
    change:function(){...}
  }
}();

Avoiding globals: step 3/3

Even better: without changing internal syntax

var myNameSpace = function(){
  var current = null;
  function init(){...}
  function change(){...}
  function verify(){...}
  return{
    init:init,
    change:change
  }
}();

This is the "revealing Module pattern" and
gives you the ability to expose methods with different names

Avoiding globals: conclusions

When possible and it makes sense, use the module pattern to protect global scope and have just a few global modules' names with specific and limited responsability

If you don't need any of your variables or functions to be available to the outside, just use an IIFE

(function(){
  var current = null;
  function init(){...}
  function change(){...}
  function verify(){...}
})();

"use strict";

"use strict"; – what?

  • Defines that JavaScript code should be executed in "strict mode".
  • It is not a statement, but a literal expression, ignored by earlier versions of JavaScript.
  • Strict mode is declared by adding "use strict"; to the beginning of a JavaScript file or a JavaScript function (scoped).
  • Strict mode changes previously accepted "bad syntax" into real errors.
  • The "use strict"; directive is only recognized at the beginning of a script or a function.

"use strict"; – changes

MDN: Strict mode - JavaScript
John Resig: ECMAScript 5 Strict Mode, JSON, and More
  • Changes converting mistakes into errors (as syntax errors or at runtime)
  • Changes simplifying how the particular variable for a given use of a name is computed
  • Changes simplifying eval and arguments
  • Changes making it easier to write "secure" JavaScript
  • Changes anticipating future ECMAScript evolution

"use strict"; – how?

Whole-script strict mode syntax

"use strict";
var v = "Hi!  I'm a strict mode script!";

Function-level strict mode syntax

function strict(){
  "use strict";
  function nested() { return "And so am I!"; }
  return "Hi!  I'm a strict mode function!  " + nested();
}
function notStrict() { return "I'm not strict."; }

Scope level strict mode

// Non-strict code...
(function(){
  "use strict";
 
  // Define your library strictly...
})();

"use strict"; – why?

  • In normal JavaScript, mistyping a variable name creates a new global variable. In strict mode, this will throw an error, making it impossible to accidentally create a global variable.
  • In normal JavaScript, a developer will not receive any error feedback assigning values to non-writable properties.
  • In strict mode, any assignment to a non-writable property, a getter-only property, a non-existing property, a non-existing variable, or a non-existing object, will throw an error.

"use strict"; – examples 1/3

Using a variable, without declaring it, is not allowed:

"use strict";
x = 3.14;                // This will cause an error (x is not defined)

Using an object, without declaring it, is not allowed:

"use strict";
x = {p1:10, p2:20};      // This will cause an error (x is not defined)

Deleting a variable (or object) is not allowed.

"use strict";
var x = 3.14;
delete x;                // This will cause an error

Deleting a function is not allowed.

"use strict";
function x(p1, p2) {}; 
delete x;                // This will cause an error 

"use strict"; – examples 2/3

Duplicating a parameter name is not allowed:

"use strict";
function x(p1, p1) {};   // This will cause an error

The string "eval" cannot be used as a variable

"use strict";
var eval = 3.14;         // This will cause an error

The string "arguments" cannot be used as a variable:

"use strict";
var arguments = 3.14;    // This will cause an error

eval() is not allowed to create variables in the scope from which it was called:

"use strict";
eval ("var x = 2");
alert (x);               // This will cause an error

"use strict"; – examples 3/3

Future reserved keywords are not allowed in strict mode.

"use strict";
var implements = 1500; // This will cause an error
var interface = 1500; // This will cause an error
var let = 1500; // This will cause an error
var package = 1500; // This will cause an error
var private = 1500; // This will cause an error
var protected = 1500; // This will cause an error
var public = 1500; // This will cause an error
var static = 1500; // This will cause an error
var yield = 1500; // This will cause an error

More examples at w3schools - JavaScript Use Strict

Tips & tricks

Literals and Constructors

Prefer object literals... ...to native built-in constructors
var o = {};
var o = new Object();
var a = [];
var a = new Array();
var re = /[a-z]/g;
var re = new RegExp("[a-z]", "g");
var s= "";
var s = new String("");
var n = 0;
var n = new Number(0);
var b = false;
var b = new Boolean(false);

Declare variables on top (be in "control" of JS hoisting)

JavaScript Scoping and Hoisting

Have exactly one var statement per scope,
at the top of the scope


/* global vars */
var donald,
    mickey = "something";

function foo(a, b, c) {
/* scope foo vars */
  var x = 1,
      bar,
      baz = "something";
}

Use Prototype to share methods

Constructor approach:

var Counter = function (counter) {
    this.counter = counter;				
    this.increment = function (b) {
        this.counter += b;
    };
};

Prototype approach:

var Counter = function (counter) { 
    this.counter = counter; 
};

Counter.prototype.increment = function (b) {
    this.counter += b;
};

Constructor vs Prototype

Useful Shorthands

If true … else Shorthand

var big;
if (x > 10) {
    big = true;
}
else {
    big = false;
}

Can become

var big = (x > 10) ? true : false;

// or even shorter

var big = (x > 10);

Test true values (trivial)

if (likeJavaScript == true)

can be simpler

if (likeJavaScript)

						

Checking null or undefined

if (variable1 !== null || variable1 !== undefined || variable1 !== '') {
     var variable2 = variable1;
} else {
     var variable2 = '';
}

Shorthand

var variable2 = variable1  || '';

Array literals and object literals

// Array
var a = new Array();
a[0] = "myString1";
a[1] = "myString2";
a[2] = "myString3";

// can be shorter
var a = ["myString1", "myString2", "myString3"];

// Object
var skillSet = new Object();
skillSet['Document language'] = 'HTML5';
skillSet['Styling language'] = 'CSS3';
skillSet['Javascript library'] = 'jQuery';

// can be written
var skillSet = {
    'Document language' : 'HTML5',
    'Styling language' : 'CSS3',
    'Javascript library' : 'jQuery',
};

Declaring variables shorthand

var x;
var y;
var z = 3;

Shorthand

var x, y, z = 3;

Assigment operators

x = x+1;
minusCount = minusCount - 1;
y = y*10;

Shorthand

x++;
minusCount --;
y *= 10;

Switch nightmare

switch (something) {
    case 1:
        doX();
    break;
    case 2:
        doY();
    break;
    case 3:
        doN();
    break;
}

Shorthand

var cases = {
    1: doX,
    2: doY,
    3: doN
};
if (cases[something]) {
    cases[something]();
}

Allow for configuration and translation

Create a configuration object that contains all the things that are likely to change over time

config = {
  CSS: {
    IDs: {
      container:'eytp-maincontainer',
      sizeControl:'eytp-sizecontrol',
      searchField:'eytp-searchfield'
    },
    classes:{
      maxvolume:'maxed',
      disabled:'disabled'
    }
  },
  application: {
    youtubeAPI:'http://gdata.youtube.com/apiplayer/cl.swf',
    volumeChange:10,
    searchResults:6
  },
  locales: {
    en: {    
      volumeMessage:'volume $x percent',
      loadingMessage:'Searching, please wait'
    },
    it: {    
      volumeMessage:'volume $x per cento',
      loadingMessage:'Ricerca in corso, attendere prego'
    },
  }
}  

jQuery performance

jQuery performance

Append Outside of Loops

DOM manipulation is slow: avoid manipulating the DOM in loops

Better:

Cache Length During Loops

In a for loop, don't access the length property of an array every time; cache it beforehand.


var myLength = myArray.length;
 
for ( var i = 0; i < myLength; i++ ) {
    // do stuff
}

Optimize loops... is it worth?

Looping on a array (using .length)

var fruits = ["apple","banana","orange","mango"];
for(var i = 0; i < fruits.length; i++) {
  // do something with fruits[i]
}

Cache length property (static length)

var fruits = ["apple","banana","orange","mango"];
for(var i = 0, max = fruits.length; i < max; i++) {
  // do something with fruits[i]
}

Yet another method (reverse while)

var fruits = ["apple","banana","orange","mango"], i = fruits.length;
while(i-=1) {  //for readability use i-- (could be slightly slower)
   // do something with fruits[i]
}

Optimize loops... probably not worth on simple arrays?

Method Chrome (48) Firefox (44) IE (11)
Using list.length 59,404 75,659 17,045
Using static len 59,488 67,460 23,547
Using reverse while 42,205 58,891 23,634

Performance given in K ops/sec

http://jsperf.com/loop-iteration-length-comparison-variations/22

Detach Elements to Work with Them

The DOM is slow; avoid manipulating it as much as possible. jQuery introduced detach() in version 1.4 to help address this issue, allowing you to remove an element from the DOM while you work with it.

var table = $( "#myTable" );
var parent = table.parent();
 
table.detach();
 
// ... add lots and lots of rows to table
 
parent.append( table );

Don’t Act on Absent Elements

jQuery won't tell you if you're trying to run a whole lot of code on an empty selection – it will proceed as though nothing's wrong. It's up to you to verify that your selection contains some elements.

// Bad: This runs three functions before it
// realizes there's nothing in the selection
$( "#nosuchthing" ).slideUp();
 
// Better:
var elem = $( "#nosuchthing" );
 
if ( elem.length ) {
    elem.slideUp();
}
 
// Best: Add a doOnce plugin.
jQuery.fn.doOnce = function( func ) {
     this.length && func.apply( this );
     return this;
 }
 
$( "li.cartitems" ).doOnce(function() {
     // make it ajax! \o/
 });

This guidance is especially applicable for jQuery UI widgets, which have a lot of overhead even when the selection doesn't contain elements.

Optimize Selectors

Selector optimization is less important than it used to be, as more browsers implement document.querySelectorAll() and the burden of selection shifts from jQuery to the browser. However, there are still some tips to keep in mind.

ID-Based Selectors

Beginning your selector with an ID is always best.

// Fast:
$( "#container div.robotarm" );
 
// Super-fast:
$( "#container" ).find( "div.robotarm" );

The .find() approach is faster because the first selection is handled without going through the Sizzle selector engine – ID-only selections are handled using document.getElementById(), which is extremely fast because it is native to the browser.

Specificity

Be specific on the right-hand side of your selector, and less specific on the left.

// Unoptimized:
$( "div.data .gonzalez" );
 
// Optimized:
$( ".data td.gonzalez" );

Use tag.class if possible on your right-most selector, and just tag or just .class on the left.

Avoid Excessive Specificity

$( ".data table.attendees td.gonzalez" );
 
// Better: Drop the middle if possible.
$( ".data td.gonzalez" );

A "flatter" DOM also helps improve selector performance, as the selector engine has fewer layers to traverse when looking for an element.

Avoid the Universal Selector

Selections that specify or imply that a match could be found anywhere can be very slow.

$( ".buttons > *" ); // Extremely expensive.
$( ".buttons" ).children(); // Much better.
 
$( ".category :radio" ); // Implied universal selection.
$( ".category *:radio" ); // Same thing, explicit now.
$( ".category input:radio" ); // Much better.

Use Stylesheets for Changing CSS on Many Elements

If you're changing the CSS of more than 20 elements using .css(), consider adding a style tag to the page instead for a nearly 60% increase in speed.

// Fine for up to 20 elements, slow after that:
$( "a.swedberg" ).css( "color", "#0769ad" );
 
// Much faster:
$( "<style type=\"text/css\">a.swedberg { color: #0769ad }</style>")
    .appendTo( "head" );

The console

You all know console.log()

...but there's much more!!

console is not standardized, but they're trying to remedy:
Console living standard

Firefox: Console - Web APIs | MDN

Chrome: Using the Console | Web Tools - Google Developers

console methods

  • console.assert()
  • console.clear()
  • console.count()
  • console.debug()
  • console.dir()
  • console.dirxml()
  • console.error()
  • console.group()
  • console.groupCollapsed()
  • console.groupEnd()
  • ...and we are just at "g"

console.log()

Format specifier

The first parameter you pass to log() may contain format specifiers, a string token composed of the percent sign (%) followed by a letter that indicates the formatting to be applied.

The following example uses the string (%s) and integer (%d) format specifiers to insert the values contained by the variables userName and userPoints:

console.log("User %s has %d points", userName, userPoints);

Styling console output with CSS

console.log("%cThis will be formatted with large, blue text", 
	"color: blue; font-size: x-large");

TL;DR

  • Use console.log() for basic logging
  • Use console.error() and console.warn() for eye-catching stuff
  • Use console.group() and console.groupEnd() to group related messages and avoid clutter
  • Use console.table() to compare objects
  • Use console.assert() to show conditional error messages

console.group()

console.group("Authenticating user '%s'", user);
console.log("User authenticated");
console.groupEnd();
// New group for authentication:
console.group("Authenticating user '%s'", user);
// later...
console.log("User authenticated", user);
// A nested group for authorization:
console.group("Authorizing user '%s'", user);
console.log("User authorized");
console.groupEnd();
console.groupEnd();

console.time()

console.time("Array initialize");
var array = new Array(1000000);
for (var i = array.length - 1; i >= 0; i--) {
    array[i] = new Object();
};
console.timeEnd("Array initialize");

Beware: console is async

Console log output can refer to a different moment in time than when called

function processObject(someObject) {
	/* do something complex with object, altering its content */
}

var o = { /* complex object */ };
console.log(o); // before processing it (you get the processed object)
processObject(o);
console.log(o); // after processing it

Actually the content of the log can be exactly the same: only the last value of o get printed

Solution: make a breakpoint (throwing an error or) using debugger

console.log(o); // before processing it
debugger;
processObject(o);
console.log(o); // after processing it
Example

WAT?

Javascript annoying facts

What's next?

  • Asyncronous programming (callbacks, async, promises)
  • Writing jQuery plugins
  • Chrome DevTools
  • HTML5 new APIs (WebStorage, WebWorkers, WebSocket, Geolocation, History api, WebRTC, MediaDevices, etc..)
  • Require.js, AMD
  • CommonJS
  • JS on the server side
  • ES6
  • Code conventions & standards
  • Linting
  • Testing

Consigli per gli acquisti

You don't know JS

Grazie!