(Some) JS best practices

Introduction

Firenze - Aperion - 17/12/2015

Maurizio Manetti

Javascript concepts (1/2)

Javascript concepts (2/2)

  • Object oriented
    • Native (ECMA standard)
      • built-in (Array, Date)
      • user-defined (var o = {};)
    • Host
    • No classes (ECMAScript 5): prototypes

Javascript data types

  • Primitives:
    • Boolean, Null, Undefined, Number, String, Symbol (ECMAScript 6)
  • Objects:
    • Function, Array, Date, RegExp, Host objects, User defined objects

Literals

"";
5;
{};

Object literals:

var sith = {
	name: "Darth Vader",
	say: function() {
		return "I am " + this.name;
	}
};
console.log(sith.say());

N.B.: object literals are not JSON
JSON is a serialization format

Custom Constructor Functions

What does new operator do?

When new Foo() is run, the following things happen:

  • A new object is created, inheriting from Foo.prototype.
  • The constructor function Foo is called with the specified arguments and is bound to the newly created object. new Foo is equivalent to new Foo(), i.e. if no argument list is specified, Foo is called without arguments.
  • The object returned by the constructor function becomes the result of the whole new expression. Unless the constructor function explicitly returns an object, the object created in step 1 is used. (Normally constructors don't return a value, but they can choose to do so if they want to override the normal object creation process.)

It's like the following is happening:

var Sith = function(name) {
	// create a new object
	// using the object literal
	// var this = {};

	// add properties and methods
	this.name = name;
	this.say = function() {
		return "I am " + this.name;
	}

	// return this;
}

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);

Primitives and wrappers

The secret life of JavaScript primitives

Arrays' peculiarities (1/2)

Arrays' peculiarities (2/2)

Functions

  • Functions are first-class objects
  • Functions provide scope
  • Can be created dinamically, at runtime
  • Can be assigned to variables
  • Can be passed as arguments
  • Can have their own properties and methods

Scope

  • Scope is the set of variables you have access to.
  • Objects and functions are also variables.
  • Scope is the set of variables, objects, and functions you have access to.
  • JavaScript has function scope: the scope changes inside functions.
  • Since local variables are only recognized inside their functions, variables with the same name can be used in different functions.
  • Local variables are created when a function starts, and deleted when the function is completed (not completely true...: see closures).

Scope example

Globals

var p = "something";
is (surprisingly?) different from
p = "something";

Variable hoisting

Function expressions / declarations

Function declaration:
function pippo(something) {
	console.log(something);
}
Unnamed function expression:
var pippo = function(something) {
	console.log(something);
};
Named function expression:
var pippo = function pippo(something) {
	console.log(something);
};
var pippo = function pluto(something) {
	console.log(something);
};
More on the topic...

Functions hoisting

Immediately invoked function expression (IIFE)

(function(){
	console.log("pippo");
}());

(function(){
	console.log("pippo");
})();

What is this?

Derick Bailey's email course

The 5 rules of JavaScript's this

  1. Basic function invocation
  2. Method calls on objects
  3. Function calls with new
  4. Using .call and .apply
  5. Using .bind
  6. (ES6 arrow functions =>)

Simple Function Invocation

The default value of this depends on your environment:
  1. A browser
  2. A browser with strict mode enabled
  3. NodeJS
  4. NodeJS with strict mode enabled
  5. ...

Method calls on object

Method calls assign this to the object
from which they are called:

A Constructor Function

Using .call

You can supply any number of arguments to the .call method, and all of them will be passed to the original function after setting the context.

doStuff.call(myContext, 1, 2, [... n]);

The .call method is most often used when you need to dynamicall call a function with a given context and a set of values that you already have as variables.

Using .apply

The use of .apply is essentially the same as that of .call, except you pass in an array as the second parameter and that array gets passed through to the original object as the list of arguments. If you try to pass more than 2 parameters to the .apply method, you'll get an error. The .apply method is most often used when you need to dynamically call a function with a given context and a set of values in an array, such as splitting the arguments object from another function.

Using .bind

The .bind method call exists on all JavaScript functions, the same as .call and .apply - at least, since ECMAScript 5. The major difference between .bind and .call / .apply, is that .bind will not invoke the function in question. Instead, it returns a new function. Invoking this new function will in turn invoke the original function with the context that was specified in the .bind call. In other words, .bind allows you to set the context of a function without invoking it.

Closures

How do JavaScript's closures work

One sentence summary:

A closure is the local scope for a function — kept alive after the function has returned.

Closures: getting started

The following code returns a reference to a function:

A reference to a function is returned to a variable (say2).

You can think of a function reference variable as having both a pointer to a function as well as a hidden pointer to a closure.

The above code has a closure because the anonymous function "function() { alert(text); }" is declared inside the function sayHello2().

When you use the function keyword inside another function, you are creating a closure.

In most common languages after a function returns, all the local variables are no longer accessible because the stack-frame is destroyed.

In JavaScript, if you declare a function within another function, then the local variables can remain accessible after returning from the function you called. This is demonstrated above, because we call the function say2() after we have returned from sayHello2(). Notice that the code that we call references the variable text, which was a local variable of the function sayHello2().

function() { alert(text); } // Output of say2.toString();

Looking at the output of say2.toString(), we can see that the code refers to the variable text. The anonymous function can reference text which holds the value 'Hello, Han Solo!' because the local variables of sayHello2() are kept in a closure.

The magic is that in JavaScript a function reference also has a secret reference to the closure it was created in.

More examples: example 1

This example shows that the local variables are not copied — they are kept by reference.

It is kind of keeping a stack-frame in memory when the outer function exits!

Example 2

All three global functions have a common reference to the same closure because they are all declared within a single call to setupSomeGlobals().

The three functions have shared access to the same closure — the local variables of setupSomeGlobals() when the three functions were defined.

Note that in the above example, if you call setupSomeGlobals() again, then a new closure is created. The old gAlertNumber, gIncreaseNumber, gSetNumber variables are overwritten with new functions that have the new closure. (Whenever you declare a function inside another function, the inside function(s) is/are recreated again each time the outside function is called.)

Example 3

Be careful if you are defining a function within a loop: the local variables from the closure do not act as you might first think.

The line result.push( function() {alert(item + ' ' + list[i])} adds a reference to an anonymous function three times to the result array. Think of it like:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

There is only one closure for the local variables for buildList. When the anonymous functions are called on the line fnlist[j](); they all use the same single closure, and they use the current value for i and item within that one closure (where i has a value of 3 because the loop had completed, and item has a value of 'item2').

Example 3 revisited

The solution here is to use an additional closure at every loop execution, that is, a function to capture the scope at every for iteration.

Example 4

This example shows that the closure contains any local variables that were declared inside the outer function before it exited. Note that the variable alice is actually declared after the anonymous function. The anonymous function is declared first; and when that function is called it can access the alice variable because alice is in the same scope (remember variable hoisting). Also sayAlice()() just directly calls the function reference returned from sayAlice() — it is exactly the same as what was done previously, but without the temporary variable.

Tricky: note also that the sayAlert variable is also inside the closure, and could be accessed by any other function that might be declared within sayAlice(), or it could be accessed recursively within the inside function.

Example 5

This final example shows that each call creates a separate closure for the local variables. There is not a single closure per function declaration. There is a closure for each call to a function.

Example 6

jQuery misuse

Example 7: getting to the module pattern

Closures are not magic

The Module pattern