JS Quick Restart for the Impatient

For polyglots who can't keep too many languages in their heads and need a deep refresher.

Why this document?

The intention is not to teach Javascript, or cover it completely. This is for programmers (like myself) with fickle memories who work in multiple languages, and when coming back to a language, want a quick refresher to get up to speed. And, as of this writing, Javascript.info is a pretty handy collection of explanations and code. And so is MDN.

Quick reminders

  • It’s functional.
  • It’s imperative.
  • It’s object oriented.
  • It’s mutable all over the place. (Useful reminder - especially when you wish to write FP-style code)
  • It’s class-oriented. Not!

The Key Syntax Highlights

Declarations

Do not forget let (and var)

And you probably certainly want to use let.

function foo() {
    // Hoisted to the top!
    bar = 10;

    // Safe-r
    let baz = 21;
    console.log("The value of baz is " + baz);
}

foo();

// 10. The bar is so low, you'll run into it here.
console.log("The bar outside is a low " + bar);

// But, we can clean up!
delete bar;
  • var is global scope, or if within a function, all of the function-scope.
  • let is safer. It’s always block scope. And let is also not hoisted to the top of the block, unlike var.
  function noop() {
      // ok, though undefined
      console.log(bar);

      // blow up! ReferenceError
      try {
          console.log(blow);
      } catch (e) {
          console.log("Error: " + e.message);
      }
      var bar = 10;
      let blow = 0;
  }
noop();

Prefer const

const is nothing but immutable reference. Like, final in Java.

  const some = {};
  console.log(some);

  // Now you can NOT reassign some to anything else
  try {
      some = {"another": "object"}; // Assignment error!
  } catch (e) {
      console.log("Error: " + e.message);
  }

  // But!
  some.message = "hey there!"; // ok
  // some is NOT immutable.

  console.log(some);

Literals

Arrays

  let arr = [1, 2, 3, 'four', 'five']

Strings

  let name = "Ram" // String
  let greeting = `Hello, ${name}` // interpolation
  console.log(greeting)

Basic types

  true
  false
  null
  undefined
  42
  31.24
  9911008195436579n // BigInt

Regular Expressions

  /^\d{5}-\d{4}$/

  /^[a-z]/i.test("AbCdEf") // case-insensitive

There are five flags

i
case-insensitive
g
match all instances instead of just the first one
m
match across multiple lines
y
sticky-matching
u
enable the use of Unicode point escapes like so \u{...}

Useful Core Data and Structure Types

MDN Reference

Data Types

  • Boolean
  • Number
  • String
  • BigInt
  • Symbol

Structural Types

  • Array
  • Map
  • Set
  • WeakMap
  • WeakSet
  • Date
  • Object (!)

Others

  • Date

Default Arguments

  function greet(greeting, entity = "World") {
      return `${greeting}, ${entity}!`
  }

  console.log(greet("Hello"))
  console.log(greet("नमस्ते", "ब्रम्हाण्ड"))

Destructuring

Arrays

Arrays represent a collection of items, and offer a wide variety of methods to work on them. These methods are extremely central to any data processing activities with collections. A good reference is at MDN - Array.

Unfortunately, most methods on the array mutate it, so it is important to plan your code accordingly.

General Ops

Part One

  // Helper - as array-methods mutate, we get fresh ones
  let f = () => ["Apple", "Banana", "Cherry", "Dragonfruit"];

  let demo = (message, array, op) => {
      console.log(`Performing: ${message}. Array = ${array}`)
      let retval = op()
      console.log(`Return value of op: ${retval}`)
      console.log(`State of array after op: ${array}`)
      console.log("-----------------------")
  }

  a = f()
  demo("Pop", a, () => a.pop())

  a = f()
  demo("Push", a, () => a.push("Mango"))

  a = f()
  demo("Shift", a, () => a.shift())

  a = f()
  demo("Unshift", a, () => a.unshift("Mango"))

Part Deux

Find, remove, copy operations. I couldn’t find an “insert at arbitrary index” method.

    let f = () => ["Apple", "Banana", "Cherry", "Dragonfruit"];

    a = f()
    b = a.slice()

    console.log(`The sliced array is ${b}`)
    // Finding an item index by the item's value
    console.log(`Banana is at position ${a.indexOf("Banana")}`)
    // Or, pass a function
    console.log(`Banana is at position ${a.findIndex(e => e === 'Banana')}`)

    // Removing 2 items starting position 1 (0-based index)
    removed = a.splice(1, 2)
    console.log(`We removed ${removed}, and the remaining array is ${a}`)

    console.log(`The sliced after the above splice operation is ${b}`)

Operations over Array contents nonmutating

  let f = () => ["Apple", "Banana", "Cherry", "Dragonfruit"];

  // Find 6-lettered fruits
  const a = f()
  selected = a.filter(fruit => fruit.length === 6)
  console.log(`The selected fruits are ${selected}`)
  console.log(`The input array stands at ${a}`)

Flatmap

return ["Apple", "Banana"].flatMap(f => f.toLowerCase())

map

return ["Apple", "Banana"].map(f => f.toLowerCase())

reduce

return ["Apple", "Banana"].reduce((a, b) => a + b)

reduceRight

return ["Apple", "Banana"].reduceRight((a, b) => a + b)

Objects. Classes.

There are various styles. It’s a matter of choice and needs. Some are easy to write, but less memory efficient. Some are a bit involved to code, but end up being more memory efficient. (You guessed it - using the prototype for common methods!)

Objects

Functions are objects too. And can be used as constructors

  function Foo() {
      this.bar = Math.random();
  }

  // foo got no bar.
  let foo1 = Foo();
  try {
      console.log(foo1.bar); // undefined
  } catch(e) {
      console.log("Error: " + e.message);
  }
  console.log(bar); // Global pollution!

  let foo2 = new Foo(); // Note the new - constuctor invocation
  console.log(foo2.bar); // foo2 is an object

It’s the this keyword that needs attention, under the influence of new.

Modify Objects. The Formal Way

// Boring stuff
// But look up

const o = {};

Object.defineProperty(o, 'immutableProperty', {
    value: 10,
    writable: false
});

console.log(o.immutableProperty); // 10
// You can't assign a new value to
// o.immutableProperty. Yay!

// For multiple properties
Object.defineProperties(o, {
    attribute1: {value: function() {return "I am a function - attribute1"}},
    attribute2: {value: "I am another attribute!"}
});

console.log(o.attribute1());
console.log(o.attribute2);

Accessor properties

Make function call (getter/setter) appear like attribute access

  let foo = {
      _val: `foo`,
      get val() { return `${this._val}-${this._val}`; },
      set val(new_val) {
          this._val = new_val;
      }
  }

  console.log(foo._val);
  console.log(foo.val); // 'foo-foo'
  foo.val = 'bar';
  console.log(foo._val);
  console.log(foo.val); // 'bar-bar'

Gives you a nice way to ensure sanity checks before setting values.

prototype and __proto__ are different things

__proto__ was a non-standard property until ECMAScript 2015, although widely defined in various implementations. prototype is typically available on objects like, well, Object, Function, Array, Date – that are used to set the __proto__ objects on newly constructed instances. The correct way to access the prototype of any object are

  • Object.getPrototypeOf() / Reflect.getPrototypeOf(), and
  • Object.setPrototypeOf() / Reflect.setPrototypeOf()

And the use of __proto__ is discouraged in favour of the above - See this.

var F = function() { this.name = "f" }
var f = new F();
console.log(f.__proto__ === F.prototype); // true
console.log(Object.getPrototypeOf(f) === F.prototype) // true
console.log(Object.getPrototypeOf(f) === f.__proto__) // true
console.log(f.prototype) // undefined

The standard way to denote this object in print is [[Prototype]]

Classes

JS supports the class keyword, and is used as follows.

  class Report {
      constructor(employee, manager, type) {
          this.employee = employee;
          this.manager = manager;
          this.type = type;
      }
      send() {
          console.log(`Sending ${this.type} report from ${this.employee} to ${this.manager}.`);
      }
  }

  new Report("Peter Gibbons", "Bill Lumbergh", "TPS").send();

The above is syntax-sugar for the following essentially

  function Report(employee, manager, type) {
      this.employee = employee;
      this.manager = manager;
      this.type = type;
  }

  Report.prototype.send = function() {
      console.log(`Sending ${this.type} report from ${this.employee} to ${this.manager}.`);
  }

  new Report("Peter Gibbons", "Bill Lumbergh", "TPS").send();

But of course, the class syntax allows for pretty easy to express domain setup. JS classes also support static members (attributes and methods), with a somewhat distinctive syntax.

  class PhoneNumber {
      areaPrefix = '';
      number = '';
      #countryCode = '+91';

      constructor(areaPrefix, number) {
          this.areaPrefix = areaPrefix;
          this.number = number;
      }

      get printable() {
          return PhoneNumber.#format(this.#countryCode, this.areaPrefix, this.number);
      }

      static #format(countryCode, prefix, number) {
          return `(${countryCode}) ${prefix}-${number}`;
      }
  }

  console.log(new PhoneNumber("20", "12345678").printable);

Prototype

Javascript uses prototypal inheritance. Object hierarchies chain up via the __proto__ attribute - an attribute which should never be directly accessed. Use the inbuilt (obviously-named) static methods on Object

  • Object.getPrototypeOf

  • Object.setPrototypeOf

  • Object.isPrototypeOf

    Prototypal inheritance is also described in terms of Differential Inheritance. In JS, derived objects are linked to their parents and the lookup of properties is dynamic. Which means, if we modify the prototype of an object at runtime, the effects are noticed on all objects that are linked to it. Which means - objects are linked, unlike class-based OO languages where characteristics are copied at creation time.

Object.create - Prototypal Inheritance

We can use proto objects to create and extend new objects. Note below how we use a “blueprint” object - stateless - and create a new object using Object.create while adding more attributes.


var stateless = {
    someFunc: function() {
        console.log(this.stateVar);
    }
}

var someInstance = Object.create(stateless, {
    "stateVar": {
        value: "Full state honors",
        writable: false, // default
        configurable: false, // default
        enumerable: true // depends
    },
    "moarStateVars" : {
        //
    }
});

someInstance.someFunc()

It gets more interesting. The “blueprint” can be dynamically updated and the derived objects get new data/behaviour. Differential inheritance.

  var stateless = {
      someFunc: function() {
          console.log(this.stateVar);
      }
  }

  var someInstance = Object.create(stateless, {
      "stateVar": {
          value: "Full state honors"
      }
  });

  someInstance.someFunc();

  try {
      someInstance.anotherFunc();
  } catch (e) {
      console.log(e.message);
  }

  stateless.anotherFunc = function() {
      console.log("Another one here - " + this.stateVar);
  }

  someInstance.anotherFunc();

  // But wait, we can *shadow* too
  someInstance.someFunc = function() {
      console.log("I refuse to honor the original contract!");
  }

  someInstance.someFunc();

Function based constructors


  function Contact(name, phone) {
      return {
          whatsYourName() { return name; },
          call() {
              console.log("Calling " + name + " at " + phone);
          }
      }
  }
  // The data in the above case is available in a closure.
  // But not for outside modification.
  // Of course, the usual pass-by-reference/value semantics apply.
  Contact("Veeru", 108).call();

  function EfficientContact(name, phone) {
      this._name = name;
      this._phone = phone;
  }
  // Everyone has access to the name and phone.

  EfficientContact.prototype.whatsYourName = function() {
      return this._name;
  }
  EfficientContact.prototype.call = function() {
      console.log("Efficiently calling " + this._name + " at " + this._phone);
  }
  // But the above methods are shared across derived instances.

  new EfficientContact('Ram', 108).call();

The Object literal

let foo = {
    attribute: 10,
    someFunction: function() {
        // Can access attributes
        console.log("My attribute = " + this.attribute);
    }
}
foo.someFunction();

Modules

The Module Pattern

let myExternalArg = 10;
let someModule = (function (arg) {
    var somePrivate = 20;
    function someOtherPrivateFunction() {
        // I also have access to the arg, okay?
    }
    // Possibly some initializer code

    return {
        // An object that captures the results
        // of initialization, local variables in this
        // scope via the closure, as well as access
        // to the argument 'arg'
        f1: function() { console.log("I know about arg = " + arg) },
        f2: function() { console.log("I also have my private state = " + somePrivate) }
    };
})(myExternalArg);
someModule.f1();
someModule.f2();

CommonJS

This is a convention used for packaging modules used in certain environments. Like nodejs.

AMD - Asynchronous Module Definition

This is another convention for packaging modules, with dependency declarations. As the name suggests, this is geared towards enabling asynchronous loading of libraries/modules. Useful when you are executing within the browser and JS sources could exist at different locations, and network latencies with linear loading will make the process slow. Especially when browsers can load in parallel.

Implementations which deal with the AMD format include libraries like RequireJS and Dojo.

Asidebrowserify and webpack

browserify can process node-like module definitions, combine sources from various files, and prepare them for use within the browser. webpack helps in creating complex source-code transformation pipelines.

ES2015 Modules

Only mentioned here for the sake of completeness (as of this writing.) The Auth0 article (mentioned in the reference) is a very good read. And this is apparently the default one to use now as it subsumes and replaces the above types/patterns.

Bindings and Closures

Dynamic bindings are a thing. The following is a window into the memory and execution models of JS.

  function foo() {
      console.log("Hello, " + this.name + "!");
  }
  foo(); // Hello, undefined!

  let context = { name: "Ram" };
  // We bind the 'this' in foo to context
  let caller = foo.bind(context);
  caller(); // Hello, Ram!

Async

Comprehensive documents on MDN: Promise.

This is too big a topic by itself. But apart from the fact that you deal with the asynchronous nature of various calls by way of passing callbacks, you can also make use of some constructs/libraries that allow you to lay out your code in a more serial manner.

Use Promise objects, if you’d like to avoid the usual callback style. But remember that Promise objects don’t let the computation outcomes escape easy. In a way, it reminds of the core.async way of Clojurescript - only, a bit different and more involved. But it has clear semantics about differentiating between success and error conditions.

To make it a bit easy for you to consume asynchronous computation, there’s a more complicated async-await route to structuring your code.

  const slowIdentity =
        (x) => new Promise(resolve => {
            setTimeout(() => {
                resolve(x);
            }, 5000);
        });

  async function awaiter() {
      console.log("I'll wait...");
      console.log("And the value is - " +
                  await slowIdentity(5));
      console.log("Ciao!");
  }

  awaiter();

Metaprogramming

A great reference at MDN: Metaprogramming. The following is a sample, reproduced from the above MDN site.

  let handler = {
      get: function(target, name) {
          console.log(`Attempting to get ${name}`)
          let retval = target[name]
          if (!retval) {
              console.log(`\tDid not find ${name}`)
              retval = 42
          }
          return retval
      }
  }

  let p = new Proxy({}, handler)
  p.a = 1
  console.log(p.a, p.b)

References