Javascript without "this"
2021-03-29Javascript's this
keyword is problematic for several reasons:
- It is hard to discuss
this
without confusion. - If you forget a
this
you can unintentionally create, or access, variables in the global scope. - Because it can be rebound, you cannot tell what value
this
has until runtime.
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:
js
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});
}