JavaScript language reference

Series of notes adapted from different books and blogs so that I can easily refer back to new concepts until they become old.

Functions

The following mostly stems from Secrets of the JavaScript Ninja (Resig, Bibeault and Maras).

Functions as objects

Functions are first-class objects, and behave just as any other JS object. As such, they may even have stored state

var someFunc = () => {};
someFunc.property = "value";

This allows function caching or memoization to be easily implemented

var fibbonaci = (n) => {
	// create cache
	if (!fibbonaci._cache) {
		fibbonaci._cache = [1, 1];
	}
	if (fibbonaci._cache.length >= n) {
		return fibbonaci._cache[n-1];
	} else {
		fibbonaci._cache.push(fibbonaci(n-1) + fibbonaci(n-2));
		return fibbonaci(n);
	}
};

If the cache were an object, we check the existence of an index of in lieu of the array length

if (fibbonaci._cache[n] === undefined) {
	// ...
} 

Function declarations

There are multiple ways to define functions, simplest of which is the function literal () => {} (here in arrow notation). There are four groups of declarations in JS

  • function declaration and expression, to the analogy of declaration and implementation in C-type languages

function name() { return true; }
  • arrow function or lambda functions, which are syntactically succinct

someArg => someArg * someArgs;
(arg1, arg2) => { return ar1 + arg2; }
  • Function constructors, allowing new functions to be constructed with help of the new keyword, even dynamically

new Function('a', 'b', 'return a + b');

For more information on function constructors, see here.

and finally

  • generator functions, much like Pythonic generators, and expressed

function* generator() { yield true; }

There are also immediate one use functions, which can be declared with

(/* function implementation */)(args);

These types of evaluations are sometimes known as immediately invoked function expressions (IIFE). They are idiomatically also seen in unitary operations on functions, which here take the role of an expression

+function(){}();
-function(){}();
!function(){}();
~function(){}();

Parameters and arguments

JS does not throw errors on function calls with more or fewer arguments than accepted parameters. Instead, those arguments are not assigned to any namespace values in the function scope, or the parameters left undefined

function someFunc(arg1, arg2) { /* ... */ }  

someFunc("hello", "there", "world");	// OK! no errors
someFunc("hello");						// OK! but arg2 is undefined

JS provides the keyword argument which stores the function argument details as an array. For the above case, the argument array is

[Arguments] { '0': 'hello', '1': 'there', '2': 'world' }
[Arguments] { '0': 'hello' }

respectively.

Similar to variadics in C++, JS has rest parameters, which stores all unnamed arguments in an array. Syntactically, this is expressed

function someFunc(arg, ...otherArgs) { /* ... */ } 

In lieu of overloading functions, JS has a method for providing succinct function defaults

// Wordy solution
function someFunc(arg) {
	arg = typeof arg === "undefined" ? "default" : arg;
	/* ... */
} 
// ES6+
function someFunc(arg = "default") {
	/* ... */
}

The this implicit argument

Functions may be invoked on their own, as a method (obj.func()), through a constructor, as in new Func(), or through apply and call prototype methods.

The this object provides the function context at face value. For example

function loose() {
	return this;		// returns window
}

function strict() {
	"use strict";
	return this;		// returns undefined
}

If the function were a method of an object, the context becomes the object itself, as we are familiar with from other ObjOrtd languages.

Arrow functions do not have an implicit this, but instead use the context at the point of definition, making them very suitable as callback functions

function Button() {
	this.clicked = false;		// context is Button
	this.click = () => {
		this.clicked =  true;	// context is still Button
	};
}

var button = new Button();

This has the caveat that if Button’s constructor were never called, as such were not instantiated to an object, the context of the lambda would default to global. To mitigate this, we define our button instead

var button = {
	clicked: false,
	click: function() {
		this.clicked = true;
	}
};

and execute a script

// button.clicked == false
button.click();
// button.clicked == true

Note we can also use bind to create a new function, with its context bound to the object passed to it, e.g. trivially

button.clicked.bind(button);	// context of this is now button

Closures

The concept of closure allows a function to access the namespace in the scope of the function definition. The closure encompasses variables and definitions in the scope and ensures they are available if needed, even once program execution has left the scope.

Mimicking private variables

Let us suppose we wanted to create read only access to a variable in a definition

function Counter() {
	var count = 0;						// scoped variable
	this.getCount = function() {
		return count;
	};
	this.nudge = function() {
		count++;
	};
}

var c = new Counter();
console.log(c.getCount());				// 0
c.nudge();								// c.count++;
console.log(c.getCount());				// 1

Note, Counter.getCount and Counter.nudge could also be defined with arrow notation. It is crucial to use the function constructor, so that a new context for the object is created.

Closures in callbacks

Were a function called at an unspecified time later on, closures provide a intuitive way of avoiding nasty pitfalls.

For example, were we to use the built-in setInterval to periodically call some callback function, we can use closure to provide the necessary control statements as to give the periodic function a simple interface

function periodicLog() {
	var tick = 1;
	var timer = setInterval(function () {
		if (tick <= 10) {
			console.log(`- tick number ${tick++}`)
		} else {
			clearInterval(timer);
		}
	}, 100);
}
periodicLog();	// outputs a tick every 100 ms

Or creating unique instances of the function also provides each instance with its own set of parameters, allowing complex properties to easily be abstracted.

Generators and promises

Leading into the notion of asynchronous programming, JS is a single threaded language, thus any waiting will deactivate the UI until the function call ends. Asynchronous function calls and callbacks can extend the function and interaction of our program considerably.

Generators

Much like Pythonic generators, JS generators are state yielding functions

function* generator() {
	for (var i = 1; i <= 10; i++) {
		yield "+".repeat(i);
	}
}

for (let val of generator()) {
	console.log(val);
}

The generator can be controlled through an iterator object

const plusCount = generator();
const item1 = plusCount.next();

The plusCount iterator instance includes several properties, an important one is plusCount.done which returns true if the generator yields have been expired. Generators may also then be traversed with

let item;
while(!(item = plusCount.next()).done) {
	console.log(item);
}

Here item is a JS object, with a value and a done attribute. The for-of loops are syntactic sugar over the above, in the same fashion as Python for-in loops, or C++ for (int i : array).

Yield statements in JS can also be used to yield another generator. Consider the example

function* generator() {
	for (var i = 1; i <= 10; i++) {
		yield "+".repeat(i);
	}
	yield* backGenerator();
}

function* backGenerator() {
	for (var i = 10; i >= 1; i--) {
		yield "+".repeat(i)
	}
}

Now, once generator has completed the for loop, it yields an instance of backGenerator which allows the iteration to continue seamlessly.

Generators have great application for streaming data, yielding unique identification codes, or traversing the DOM.

Generators may also have data send back to them, just like with Python’s x = yield statement and send() method.

The sending syntax in JS is

function* generator() {
	for (var i = 1; i <= 10; i++) {
		i += yield "+".repeat(i);
	}
}

let plusCount = generator();
let item;
while(!(item = plusCount.next(3)).done) {	// here we send 3
	console.log(item);
}

Or we can use .throw(/* what */) to throw an exception in the iterator at yield.

Note that generators still have access to the return keyword, allowing them to give a value to their instance upon completion.

Promises

Promises are the main driver behind writing succinct asynchronous code. They provide an implementation for allowing function chains to be established off of the basis of a result or error case. A promise may be defined

const p = new Promise((resolve, reject) => {
	/* ... */
});

p.then(
	(/* results of Promise lambda */) => { /* ... */},
	(/* error case */) => { /* ... */}
);

The error callback of p may also be chained instead with .catch(callback) instead of providing a second argument to .then().

Promises also allow multiple asynchronous tasks to be fed together, using the .all() method. For, e.g. parallel gathering of information, we can write

Promise.all([
		makeRequest(url1),
		makeRequest(url2),
		//...,
		makeRequest(urlN)
	])
	.then(results => {
		console.log("url1 : " + results[0])
		/* etc */
	})
	.catch(error => {
		/* error handle */
});

Similar syntax is also used to obtain the first result of a series of asynchronous tasks using .race(). The result argument is now just the result of a single function, instead of the list of inputs.

Combining generators with promises

We can combine generators with promises and the concept of closure to write strong asynchronous code. Note, this is not a production grade implementation, and indeed the async() demonstrated has default language implementations

async(function*() {
	try {
		const key = yield somePromise();
		const val = yield someIndex(key);
		// all information received
	} catch (e) {
		// error handling
	}
});

function async(generator) {
	var itt = generator();

	function handle(ittResult) {
		if (ittResult.done) return;

		const ittVal = ittResult.value;
		if (ittVal instanceof Promise)
			ittVal.then(res => handle(itt.next(res)))
				  .catch(err => itt.throw(err));
	}
	try {
		handle(itt.next())
	} catch (e) { itt.throw(err); }
}

JS introduces the async and await keywords to help integrate promises and generators, such that our above code may be rewritten using the new syntax

(async function() {
	try {
		const key = await somePromise();
		const val = await someIndex(key);
		// all information received
	} catch (e) {
		// error handling
	}
})();

Object orientation and prototypes

Prototypes in JS are objects to which the property lookup is delegated, allowing properties and functionality to automatically be accessible to other objects. They can be thought of as classes in other object orientated languages. Prototypes allow for inheritance within JS.

Inheritance with .setPrototypeOf()

We can mimic inheritance using the Object.setPrototypeOf() method

const assert = require('assert')

const athlete = { instruct: true };
const scientist = { research: true };
const politican = { lie: true };

assert("research" in scientist, "Scientist can research.");
assert(!("research" in athlete), "Athlete cannot research.");
// set prototype
Object.setPrototypeOf(athlete, scientist);

assert("research" in athlete, "Athlete can now research.");

Object.setPrototypeOf(athlete, politican);
assert("lie" in athlete, "Athlete can now lie.");

The calling order in the case of same identifier is always the inheriting object’s attribute/method, then that of the prototype, and further up the chain.

Constructors

We can prototype methods using a function constructor and the new keyword

function SomeObject() {}	// no implementation
SomeObject.prototype.action = function() {
	console.log("Hello World"); 
};

const instance = new SomeObject();
instance.action();

Instance prototype properties

Here is an example of initialization precedence

function SomeObject() {
	this.acted = false;
	this.action = function() {
		console.log(!this.acted);
	};
}

SomeObject.prototype.action = function() {
	console.log(this.acted);
};

const instance = new SomeObject();
instance.action();					

In this case, .action() will result in true being printed.

Since JS is a dynamic language, prototype changes defined or altered after an object has been instantiated still apply to that instance. Consider the snippet following on from the last

SomeObject.prototype.action = () => {
	console.log("Altered action!");
};

instance.action();		// Altered action!

// Completely override the prototype object
SomeObject.prototype = {
	action : () => {
		console.log("New prototype, who this?")
	}
}

instance.action();		// still Altered action!

// New instance has the new prototype
(new SomeObject())		// New prototype, who this?
	.action();

The instance still holds reference to the old prototype, but once the prototype has been overridden, becomes inaccessible.

Object typing with constructors

Prototypes also store information on how the instance was constructed. For instance, we can access the constructor function using the .constructor property of the instance

function SomeObject() {}
const instance = new SomeObject();
console.log(instance.constructor);			// [Function: SomeObject]

The instance also returns true for ininstanceof SomeObject calls. We can also create new instances of SomeObject by calling

const newInstance = new instance.constructor();
console.log(newInstance === instance)		// false

Achieving inheritance

We already saw how inheritance can be mimicked using the .setPrototypeOf() method, but we can also achieve inheritance with objects or instances. The common idiom is to write

function Super() {}
Super.prototype.action = function() { console.log("Action from Super.") };

function SomeObject() {}
SomeObject.prototype = new Super();
(new SomeObject).action();				// Action from Super.

Note if the new keyword were not used, the prototype is not properly endowed into Super, and similarly, as the constructor is an empty function, the prototype of the instance would be undefined.

The problem with this implementation is that the check

(new SomeObject).constructor === SomeObject;

will fail, as it will yield Super instead. We will come back to a solution to this later, but for now we need to examine the JS object properties in order to find an appropriate solution.

Configuring object properties

JS describes every object property with a descriptor, which is controlled with the keys

  • configurable: if true, property’s descriptor can be changed or deleted. else cannot do either

  • enumerable: if true, property show up during for-in loops

  • value: specifies the value of the property (default is undefined)

  • writable: if true, property can be changed using an assignment

  • get: defines getter function (cannot be defined in conjunction with value and writable)

  • set: defines a setter function (same restrictions as get)

A default property, such as

someInstance.prop = "someValue";

has configurable, enumerable, and writable set to true, the value is someValue, and the getter and setter functions would be undefined. We can fine tune properties using the Object.defineProperty method; for example

var SomeObject = {};
Object.defineProperty(SomeObject, "prop", {
	configurable: false,
	enumerable: false,
	value: "someValue",
	writable: true
});

To solve the issue of losing the original prototype (i.e. the constructor property), we can instead define the constructor value after altering the prototype; as above, we have

function Super() {}
function SomeObject() {}
SomeObject.prototype = new Super();

but now add

Object.defineProperty(SomeObject.prototype, "constructor", {
	enumerable: false,
	value: SomeObject,
	writable: true
});

var someInstance = new SomeObject();

Now the check

(new SomeObject).constructor === SomeObject;

will pass.

JS class keyword

Recent versions of JS also include the familiar class keyword to abstract a lot of the inheritance features. A traditional class may then be defined

class SomeClass {
	constructor(someProperty) {
		this.prop = someProperty;
	}

	someMethod() {
		return this.prop;
	}

}

Here, the class keyword acts to abstract the syntax of defining prototype attributes, i.e., the above is equivalent to

function SomeClass (someProperty) {
	this.prop = someProperty;
}
SomeClass.prototype.someMethod = function() {
	return this.prop;
};

The JS classes open up use of static methods. We can declare a method as static by simply using it as a keyword

class SomeClass {
	constructor (prop) {
		this.prop = prop;
	}
	static someStaticMethod(instance1, instance2) {
		return instance1.prop - instance2.prop;
	}
}

In JS, static methods are not ‘known’ by class instances, but instead accessed through the class object

var someInstance = new SomeClass(2);
var someOtherInstance = new SomeClass(1);

var diff = SomeClass.someStaticMethod(someInstance, someOtherInstance);	// 1

Additionally, inheritance is a lot easier to implement. We can use the familiar Java extends keyword, such as

class SomeExtendedClass extends SomeClass {
	constructor(prop, newprop) {
		super(prop);
		this.newprop = newprop;
	}
}

Getters and setters

Also included in modern JS version are the keywords get and set used to define getters and setters for object properties. We can use them

const someCollection = {
	anArray = ["value1", "value2", "value3"],
	get firstItem() {
		return this.anArray[0];
	}

	set firstItem(val) {
		this.anArray[0] = val;
	}
};

This implementation also works for the class method. An alternative way to define getters and setters uses the Object.defineProperty method

function SomeObject() {
	let _counter = 0;

	Object.defineProperty(this, 'counter', {
		get: () => {
			return _counter;
		},
		set: (val) => {
			_counter += val;
		}
	});
}

Proxies and access control

Proxies allow us to execute additional routines when interacting with an object. They are, in many ways, generalizations of getters and setters. There exist many traps we can set up using the Proxy built in, such as

  • apply, activated when calling a function

  • construct, activated when using the new operator

  • get and set, used as surrogates for getters and setters

  • getPrototypeOf, setPrototypeOf, which are pretty self-explanatory

  • enumerate, activated in for-in statements.

A full list of traps can be found in the Mozilla reference. For example, using the apply trap, we can write

const sum = function(arg1, arg2) { return arg1 + arg2; };
const proxySum = new Proxy(sum, {
	apply: (target, thisArg, argumentsList) => {
		return 0;
	}
});
console.log(proxySum);			// [Function: sum]
console.log(proxySum(1, 2));	// 0

Proxies are a fantastic way of implementing logging or performance checking code. The cost of proxies is performance, however, and can incur a considerable speed decrease for the additional control.

Array methods

Manipulating data in JS is facilitated by the extensive Array object. An array object may be manipulated dynamically with

  • .push(item): add item to the end of the array

  • .unshift(item): add item to the start of the array

  • .pop(): returns and removes the last item of array

  • .shift(): returns and removes first item in array

.forEach()

Iterating over arrays is also made easy with several built-in methods. A simple for loop, such as

for (let i = 0; i < array.length; i++) {
	var item = array[i];
	// operator on item
}

is more elegantly expressed with the asynchronous

array.forEach((item, i) => {
	// operate on item
	});

Full documentation can be found here.

.map()

Creating a new array from properties in an array of objects is a common idiom, known as a ‘map’. Verbosely, a map is equivalent to

const newArray = [];
array.forEach(item => {
	newArray.push(item.prop);
});

or, using the built-in

const newArray = array.map(i => i.prop);

Full documentation can be found here.

Logical checks

Checking if every item in array has some specific property

const allHaveProperty = array.ever(i => 'prop' in i);
// true only if every i has i.prop

Checking if some items in array have a specific property

const someHaveProperty = array.some(i => 'prop' in i);
// true if at least one i has i.prop

Not that .some() will act the callback on each item until some true case is found, and then return true.

Searching

To find one and return one item with a given property, use

const item = array.find(i => 'prop' in i);
// returns undefined if no i with i.prop

To find all items with a given property

const items = array.filter(i => 'prop' in i);

Sorting arrays

Arrays can be sorted by returning numerical values, for example

array.sort((a, b) => a - b);

If

  • a - b > 0, a should come after b

  • a - b < 0, a should come before b

  • a - b = 0, a and b are on equal footing

This means we can easily implement a reversal algorithm

array.sort((a, b) => b - a);
// [1, 2, 3] -> [3, 2, 1]

.reduce()

Aggregating, e.g., a sum, would be conventionally expressed through

const sum = 0;
array.forEach(i => {
	sum += i.value;
});

or, using the built-in method

const sum = array.reduce((aggr, i) => {
	return aggr += i;
}, 0);

Here, 0 passed as the second argument is the starting value of the aggregate variable aggr.