JavaScript Programming Paradigms

JavaScript is a bit confusing for developers experienced in class-based languages like Java or C++. One of the characteristics of a language is its support for particular programming paradigms. JavaScript programs tend to make heavy use of passing functions around so they are called functional languages despite having variables and many imperative constructs.

const paradigms = [
  {name: 'Imperative', support: true, parent: null},
  {name: 'Declarative', support: true, parent: null},
  {name: 'Functional', support: true, parent: 'Declarative'},
  {name: 'Object-Oriented', support: true, parent: 'Imperative'},
  {name: 'Event-Driven', support: true, parent: null},
  {name: 'Quantum', support: false, parent: null},
  {name: 'Symbolic', support: false, parent: null}
]
/* Imperative Pattern */
for (let i = 0; i < paradigms.length; i++) {
  if (paradigms[i].support === true) {
    console.log(paradigms[i])
  }
}
/* Declarative Pattern */
const jsParadigms = paradigms.filter((p) => p.support === true)
console.log(jsParadigms)

A programming paradigm is a style, or “way,” of programming, they are not meant to be mutually exclusive. A single program can feature multiple paradigms. Very few languages implement a paradigm 100% — when they do, they are pure. It is incredibly rare to have a pure OOP language or a pure functional language.

Let’s look at common programming paradigms you might encounter over the years:

  • Imperative: programming with an explicit sequence of commands that update state.
  • Declarative: programming by specifying the result you want, not how to get it.
  • Structured: programming with clean, goto-free, nested control structures.
  • Procedural: imperative programming with procedure calls.
  • Functional: programming with function calls that avoid any global state.
  • Event-driven: programming with emitters and listeners of asynchronous actions.
  • Object-oriented: programming by defining objects that send messages to each other, objects have their own internal state and public interfaces. In class-based category objects get state and behavior based on membership in a class. While in prototype-based category objects get behavior from a prototype object.
  • Check out the exhausted list here

This post ignores the complexity of parent-child relationship between above programming paradigms and focuses more on those supported by JavaScript.

JavaScript supports functional programming

Functional programming follows a simple principle: programs are built mostly with a handful of very small, very reusable, very predictable pure functions. Pure function will always return the same output regardless of the number of times the function is called, can be safely applied with no side-effects.

Functional programming brings many benefits to your programs:

  • There are no commands, only side-effect free expressions
  • Code is much shorter, less error-prone, and much easier to prove correct
  • There is more inherent parallelism, so good compilers can produce faster code
  • The ability to change implementation without impacting the rest of the program
  • The ability to move code around freely between files, modules, projects, and so on

Lisp, Erlang, Haskell, Closure, and Python are famous functional programming languages; Haskell is a purely functional programming language in the sense that it allows no other paradigms. Functional programming is a natural fit for JavaScript because it offers first-class functions, closures, and simple lambda syntax.

JavaScript has first-class functions — it makes easy to assign functions to variables, pass them into other functions, return functions from other functions, compose functions, and so on.

// Return function from other function
function doSomething() {
  return function () {
    console.log('Hello There!')
  }
}

// Asign function to a variable
const hello = function () {
  console.log('Hello There!')
}

JavaScript has closures — is the combination of a function and the lexical environment within which that function was declared; simply define a function inside another function, and expose the inner function, either by returning it, or passing it into another function. For every closure we have three scopes: local scope, outer functions scope, and global scope.

// global scope
var e = 10
function sum(a) {
  return function sum2(b) {
    return function sum3(c) {
      // outer functions scope
      return function sum4(d) {
        // local scope
        return a + b + c + d + e
      }
    }
  }
}

JavaScript has lambda syntax — allows functions to be passed around as data, uses fat arrow notation => as lambda operator, and its body can be expression or statement block.

// has an expression as its body
const double = (value) => value * 2

// has a statement block as its body
const triple = (value) => {
  return value * 3
}

function multiply(arr, func) {
  return arr.map((value) => func(value))
}

const arr = [1, 2, 3, 4, 5]
const doubledArr = multiple(arr, double)
const tripledArr = multiple(arr, triple)

When working with functional programming paradigm we also encounter following patterns, it’s very useful and powerful, it’s important to understand what they are, how they work, and how to put them to good use.

  • Function composition is defined as passing the output of one function into input of another so as to create a combined output. The same is possible in functional programming since we are using pure functions.
  • Higher-order functions are functions that accept a function as an argument and return a function. Often, they are used to add to the functionality of a function.
  • Curried functions are functions that take multiple arguments one at a time, the benefit of currying is memoization; we can now memoize certain arguments in a function call so that they can be reused later without duplication and re-computation.

JavaScript supports prototype-based object-oriented programming

The program is written as a collection of classes and object which are meant for communication. The smallest and basic entity is object and all kind of computation is performed on the objects only.

This paradigm emphasizes on data rather procedure, it can handle almost all kind of real life problems which are today in scenario with many benefits like data security, inheritance, code reusability, flexibility, and abstraction.

When it comes to inheritance, JavaScript supports it in a process of reusing existing objects via delegation that serve as prototypes, also known as prototypal inheritance.

Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype. By definition, null has no prototype, and acts as the final link in this prototype chain.

// a function has a default prototype property as follow
{
  constructor: Ć’ doSomething(),
  __proto__: {
    constructor: Ć’ Object(),
    hasOwnProperty: Ć’ hasOwnProperty(),
    isPrototypeOf: Ć’ isPrototypeOf(),
    propertyIsEnumerable: Ć’ propertyIsEnumerable(),
    toLocaleString: Ć’ toLocaleString(),
    toString: Ć’ toString(),
    valueOf: Ć’ valueOf()
  }
}

The language runtime is capable of dispatching the correct method or finding the right piece of data simply by following prototype chain until a match is found.

Objects are mutable in JavaScript, so we can augment the new instances, giving them new fields and methods. These can then act as prototypes for even newer objects. We don’t need classes to make lots of similar objects, objects inherit from objects.

JavaScript supports event-driven programming

Event-driven programming is a programming paradigm in which the flow of the program is determined by events such as user actions (mouse clicks, key presses), sensor outputs, or messages from other programs or threads.

In an event-driven application, there is generally a main loop that listens for events, and then triggers a callback function when one of those events is detected.

Event-driven programs can be written in any programming language, although the task is easier in languages that provide high-level abstractions, such as await and closures.

JavaScript language itself doesn’t support I/O but relied on host environments. To coordinate events, user interaction, scripts, rendering, networking, and so forth, host environments must use event loop.

Conclusion

Obviously, most programs need to produce output, so complex programs usually can’t be composed using only functional programming, but wherever it’s practical, it’s a good idea mix multiple paradigms to achieve expected result. Each has their own benefits and drawbacks, mix them nicely to build products which are performant, reusable and maintainable.