Javascript without “this”

Published 2021-03-29

Javascript’s this keyword is problematic for several reasons:

To demonstrate point 2:

let bar = "my special value";
function foo(n) {
this.bar = 2;
// Here we forget the this. prefix on bar, accessing instead of global bar.
this.baz = Math.pow(n, bar);
// Here we create a var, but it isn't local to our function, it is global.
tmp = "hello world";
return baz;
}
foo(1); // → NaN
console.log(tmp); // -> "hello world"

To demonstrate the third point:

function currency(amount) {
prefix = "$";
return this.prefix + parseFloat(amount).toFixed(2);
}
currency("10"); // → '$10.00'
const currency2 = currency.bind({ prefix: "" });
currency2("10"); // → '€10.00'
currency.call({}, "5"); // → 'undefined5.00'

It is much easier to reason about code when referential transparency is maintained — the code as written should be sufficient to reason about the system without needing to know what data is bound at runtime.

How do we avoid this?

The simple & contrived examples above are easy to re-write without this:

let bar = "my special value";
function foo(n) {
const bar = 2;
return Math.pow(n, bar);
}
function currency(amount) {
const prefix = "$";
return prefix + parseFloat(amount).toFixed(2);
}

It becomes more challenging when you need a group of functions that share state:

const Queue = function () {
this.items = {};
this.head = 0;
this.tail = 0;

this.enqueue = function (item) {
this.items[this.tail] = item;
this.tail++;
};

this.dequeue = function () {
const item = this.items[this.head];
delete this.items[this.head];
this.head++;
return item;
};

this.peek = function () {
return this.items[this.head];
};

this.length = function () {
return this.tail - this.head;
};
};

When this is the case — when you need isolated state accessible to a group of functions, a closure is the solution.

const makeQueue = () => {
let items = {};
let head = 0;
let tail = 0;

const enqueue = (item) => {
items[tail] = item;
tail++;
}

const dequeue = () => {
const item = items[head];
if (item === undefined) {
return item;
}
delete items[head];
head++;
return item;
}

const peek = () => {
return items[head];
}

const length = () => {
return tail - head;
}

return Object.freeze({enqueue, dequeue, peek, length});
}