JS Quick Restart for the Impatient
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
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.
Aside – browserify 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)