JavaScript fundamentals for better developers

JavaScript has primitives, objects and functions. All of them are values. All are treated as objects, even primitives.

Primitives

Number, boolean, string, undefined and null are primitives.

Number

There is only one number type in JavaScript, the double precision floating point. Decimal numbers’ arithmetic is inexact.

As you may already know, 0.1 + 0.2 does not make 0.3 . But with integers, the arithmetic is exact, so 1+2 === 3 .

Numbers inherit methods from the Number.prototype object. Methods can be called on numbers:

(123).toString();  //"123"
(1.23).toFixed(1); //"1.2"

There are global functions for converting to numbers : parseInt()parseFloat()and Number():

parseInt("1")       //1
parseInt("text")    //NaN
parseFloat("1.234") //1.234
Number("1")         //1
Number("1.234")     //1.234

Invalid arithmetic operations or invalid conversions will not throw an exception, but will result in the NaN “Not-a-Number” value. isNaN() can detect NaN .

The + operator can add or concatenate.

1 + 1      //2
"1" + "1"  //"11"
1 + "1"    //"11"

String

A string stores a series of Unicode characters. The text can be inside double quotes "" or single quotes ''.

Strings inherit methods from String.prototype. They have methods like : substring()indexOf() and concat() .

"text".substring(1,3) //"ex"
"text".indexOf('x')   //2
"text".concat(" end") //"text end"

Strings, like all primitives, are immutable. For example concat() doesn’t modify the existing string but creates a new one.

Boolean

A boolean has two values : true and false .
The language has truthy and falsy values.
falsenullundefined''(empty string), and NaN are falsy. All other values, including all objects, are truthy.

The truthy value is evaluated to true when executed in a boolean context. Falsy value is evaluated to false. Take a look at the next example displaying the false branch.

let text = '';
if(text) {
  console.log("This is true");
} else {
  console.log("This is false");
}

Objects

An object is a dynamic collection of key-value pairs. A key is a string. The value can be a primitive, object, or function.

The simplest way to create an object is to use an object literal:

let obj = {
  message : "A message",
  doSomething : function() {}
}

We can read, add, edit and remove an object’s properties at any time.

  • get: object.nameobject[expression]
  • set: object.name = value, object[expression] = value
  • delete: delete object.namedelete object[expression]
let obj = {}; //create empty object
obj.message = "A message"; //add property
obj.message = "A new message"; //edit property
delete obj.message; //delete property

Objects can be used as hash-maps. A simple hash-map can be created using Object.create(null) :

let french = Object.create(null);
french["yes"] = "oui";
french["no"]  = "non";
french["yes"];//"oui"

If you want to make the object immutable, use Object.freeze() .

All object’s properties are public. Object.keys() can be used to iterate over all properties.

function logProperty(name){
  console.log(name); //property name
  console.log(obj[name]); //property value
}
Object.keys(obj).forEach(logProperty);

Primitives vs. Objects

Primitives are treated like objects, in the sense that they have methods but they are not objects.

Primitives are immutable, and objects are mutable.

Variables

Variables can be defined using varlet and const.

var declares and optionally initializes a variable. The value of a variable that is not initialize is undefined . Variables declared with var have a function scope.

The let declaration has a block scope.

A variable declared with const cannot be reassigned. Its value, however, can still be mutable. const freezes the variable, Object.freeze() freezes the object. The const declaration has a block scope.

The scope of a variable declared outside any function is global.

Array

JavaScript has array-like objects. An array is implemented using an object. Elements are accessed using their indices. Indices are converted to strings and used as names for retrieving values. In the next example arr[1] gives the same value as arr['1'] : arr[1] === arr['1'] .

A simple array like let arr = ['A', 'B', 'C'] is emulated using an object like the one below:

{
  '0': 'A',
  '1': 'B',
  '2': 'C'
}

Removing values from the array with delete will leave holes. splice() can be used to avoid the problem, but it can be slow.

let arr = ['A', 'B', 'C'];
delete arr[1];
console.log(arr); // ['A', empty, 'C']
console.log(arr.length); // 3

JavaScript’s arrays don’t throw index out of range exceptions. If the index is not available, it will return undefined.

Stack and queue can easily be implemented using the array methods:

let stack = [];
stack.push(1);           // [1]
stack.push(2);           // [1, 2]
let last = stack.pop();  // [1]
console.log(last);       // 2
let queue = [];
queue.push(1);           // [1]
queue.push(2);           // [1, 2]
let first = queue.shift();//[2]
console.log(first);      // 1

Functions

Functions are independent units of behavior.

Functions are objects. Functions can be assigned to variables, stored in objects or arrays, passed as an argument to other functions, and returned from functions.

There are three ways to define a function:

  • Function Declaration (aka Function Statement)
  • Function Expression (aka Function Literal)
  • Arrow Function

The Function Declaration

  • function is the first keyword on the line
  • it must have a name
  • it can be used before definition. Function declarations are moved, or “hoisted”, to the top of their scope.
function doSomething(){}

The Function Expression

  • function is not the first keyword on the line
  • the name is optional. There can be an anonymous function expression or a named function expression.
  • it needs to be defined, then it can execute
  • it can auto-execute after definition (called “IIFE” Immediately Invoked Function Expression)
let doSomething = function() {}

Arrow Function

The arrow function is a sugar syntax for creating an anonymous function expression.

let doSomething = () => {};

Arrow functions don’t have their own this and arguments.

Function invocation

A function, defined with the function keyword, can be invoked in different ways:

  • Function form
doSomething(arguments)
  • Method form
theObject.doSomething(arguments)
theObject["doSomething"](arguments)
  • Constructor form
new doSomething(arguments)
  • Apply form
 doSomething.apply(theObject, [arguments])
 doSomething.call(theObject, arguments)
  • Bind form
let doSomethingWithObject = doSomething.bind(theObject);
doSomethingWithObject();

Functions can be invoked with more or fewer arguments than declared in the definition. The extra arguments will be ignored, and the missing parameters will be set to undefined.

Functions have two pseudo-parameters: this and arguments.

this

this represents the function’s context. Only functions defined with the function keyword have their own this context. Its value depends on how the function was invoked. See below the values of this depending on the doSomething() function invocation form:

function doSomething(){
  console.log(this)
}
+------------+------------------+
| Form       | this             |
+------------+-------------------
| Function   | window/undefined |
| Method     | theObject        |
| Constructor| the new object   |
| apply      | theObject        |  
| bind       | theObject        |    
+------------+------------------+

arguments

The arguments pseudo-parameter gives all the arguments used at invocation. It’s an array-like object, but not an array. It lacks the array methods.

function reduceToSum(total, value){
  return total + value;
}
  
function sum(){
  let args = Array.prototype.slice.call(arguments);
  return args.reduce(reduceToSum, 0);
}
sum(1,2,3);

An alternative is the new rest parameters syntax. This time args is an array object.

function sum(...args){
  return args.reduce(reduceToSum, 0);
}

return

A function with no return statement returns undefined. Pay attention to the automatic semi-colon insertion when using return. The following function will not return an empty object, but rather an undefined one.

function getObject(){ 
  return 
  {
  }
}
getObject()

To avoid the issue, use { on the same line as return :

function getObject(){ 
  return {
  }
}

Dynamic Typing

JavaScript has dynamic typing. Values have types, variables do not. Types can change at run time.

function log(value){
  console.log(value);
}
log(1);
log("text");
log({message : "text"});

The typeof() operator can check the type of a variable.

let n = 1;
typeof(n);   //number
let s = "text";
typeof(s);   //string
let fn = function() {};
typeof(fn);  //function

A Single Thread

The main JavaScript runtime is single threaded. Two functions can’t run at the same time. The runtime contains an Event Queue which stores a list of messages to be processed. There are no race conditions, no deadlocks. However, the code in the Event Queue needs to run fast. Otherwise the browser will become unresponsive and will ask to kill the task.

Exceptions

JavaScript has an exception handling mechanism. It works like you may expect, by wrapping the code using the try/catch statement. The statement has a single catch block that handles all exceptions.

It’s good to know that JavaScript sometimes has a preference for silent errors. The next code will not throw an exception when I try to modify a frozen object:

let obj = Object.freeze({});
obj.message = "text";

Strict mode eliminates some JavaScript silent errors. "use strict"; enables strict mode.

Prototype Patterns

Object.create(), Constructor Function, and class build objects over the prototype system.

Consider the next example:

let service = {
 doSomething : function() {}
}
let specializedService = Object.create(service);
console.log(specializedService.__proto__ === service); //true

I used Object.create() to build a new object specializedService which has the service object as its prototype. This means that doSomething() is available on the specializedService object. It also means that the __proto__property of specializedService points to the service object.

Let’s now build a similar object using class.

class Service {
 doSomething(){}
}
class SpecializedService extends Service {
}
let specializedService = new SpecializedService();
console.log(specializedService.__proto__ === SpecializedService.prototype);

All methods defined in the Service class will be added to the Service.prototype object. Instances of the Service class will have the same prototype (Service.prototype) object. All instances will delegate method calls to the Service.prototype object. Methods are defined once on Service.prototype and then inherited by all instances.

Prototype chain

Objects inherit from other objects. Each object has a prototype and inherits their properties from it. The prototype is available through the “hidden” property __proto__ .

When you request a property which the object does not contain, JavaScript will look down the prototype chain until it either finds the requested property, or until it reaches the end of the chain.

Functional Patterns

JavaScript has first class functions and closures. These are concepts that open the way for Functional Programming in JavaScript. As a result, higher order functions are possible.

Closure is an inner function that has access to the parent function’s variables, even after the parent function has executed.

A higher order function is a function that takes another function as an input, returns a function, or does both.

For more on the JavaScript functional side take a look at:

Discover the power of first class functions

How point-free composition will make you a better functional programmer

Here are a few function decorators you can write from scratch

Why you should give the Closure function another chance

Make your code easier to read with Functional Programming

Leave a Reply

Your email address will not be published. Required fields are marked *