My most hated JavaScript features
I do love using JavaScript, it is such a powerful flexibly language. However it is not without it faults and here are some of the more annoying ones I have stumbled across in my time with it.
TLDR;
- Null data type
- Typeof operator
- Automatic semicolon insertion
- Integer support
- ES6 Class binding
- Pass-by-ref and lack of Immutability
Null data type
“Clean code” mentions
As soon as you assign NULL you start looking for it
This means you need to include logic checks specifically for Null, and failing to do so could cause fatal issues.
It proposes you should be able to use “function friendly” logic that fits with what you are doing. We should avoid assigning or returning Null as it should remain an indication of a problem.
NULL is falsely in Javascript, but there are several instances where it can easily be buggy:
- Default arguments treat Null as valid, i.e. pass Null into
someFunc(someParam = ‘default value')
andsomeParam
will be Null. - In
JSON.stringify
Null values are included butundefined
values are not. - For libraries such
lodash
it is a valid value in. i.e.item.get(nullValue, default)
will return our constant with Null assignednullValue
. This is because lodash.get only defaults onundefined
.
Some languages such as F# and Scala don’t have Null. However the pro’s of having Null tend to outweigh the cons. Hence why so many languages (C/C++ and Java included) use it.
For instance say you wanted to store if a person has hayfever, how would you store it on initialisation?
false
-> thats a valid “not allergic”0
-> Could also be miscontrued as “not allergic”""
-> Empty string could work but could be confusingNull
-> seems to describe it perfectly. “Not yet set”
Basically sometimes you do need to represent nothing, but doesn’t stop it being a pain.
Typeof operator
The main reason this guy is such an issue is because it is so useful, but returns such inconsistent results. For example:
typeof([])
ortypeof(null)
ortypeof(/regex/)
ortypeof(new Date())
ALL equalObject
(??)typeof(NaN)
(not-a-number) equalsNumber
(??). You need to useisNaN(value)
.
I try and use instanceof
more, if its applicable. The only solution to this I’ve found is using the below snippet to create a new type checker.
const typeCheck = function (o) {
var s = Object.prototype.toString.call(o);
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
}
typeCheck({}); // "object"
typeCheck([]); // "array"
typeCheck(5); // "number"
typeCheck(null); // "null"
typeCheck(); // "undefined"
typeCheck(/abcd/); // "regex"
typeCheck(new Date()); // "date"
But rolling your own solution often causes more problems than its worth.
Automatic semicolon insertion
In Douglas Crockford’s booked titled: JavaScript: The Good Parts he goes into detail as to why it is a dangerous feature, I can cover the basics here.
For clarification ASI is when JavaScript inserts a semicolon into a statement as the spec requires all statements be terminated with semicolons. However the engine can do it for you, they are optional.
An example of when this can cause bugs is:
function someFunc() {
return
{
prop: true
};
}
someFunc();
// returns undefined
// WHY -> as ASI has terminated the return statement, meaning the block below it is not returned or run.
It is am implicit feature but error resistance should always be explicit, otherwise it is easy to misunderstand. We should favour syntax that is error resistant and explicit.
Integer support
The short is that JavaScript doesn’t have integers, everything is a 64-bit Floating Point Number with a maximum number of decimals of 17. Unfortunately the prevailing issue is that they have limited precision.
This becomes a problem when doing floating point arithmetic, which produces results which aren’t always right. The famous example is 0.2 + 0.1 !== 0.3
. This kind of bug can be especially dangerous when handling money, something which you can’t afford (whey ;) ) to be sloppy with. Other languages make use of Fixed point precision due to this reason.
The solution to this is to use an additional library to help with such calculations. Not great.
I believe ES7 is looking to introduce a BigInt
and BigDecimal
primitives which will solve this natively. They will represent Integers with arbitrary precision so you can handle numbers beyond the limit of the Number
data type.
ES6 Class binding
With ES6 Classes were introduced, internally they are syntactic sugar over prototype-based inheritance.
The problem is that as JavaScript doesn’t yet have semantics where we can easily mark every class method as bound to that class (internally they are objects), there are certain scenarios which do not work.
It is very common for anyone experienced with React
(although I think with React
you can now auto-bind property methods with myFunction = () => {}
).
An example of the problem in a normal Class is:
Class Logger {
printName (name = 'there') {
this.print(`Hello ${name}`);
}
print (text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName();
// error - can't find print
The solution to this is to bind property methods in the constructor, e.g.
constructor () {
this.printName = this.printName.bind(this);
}
OR to introduce a library such as autobind
which will handle this for you automagically. e.g.
constructor() {
autoBind(this);
}
It is possible to also solve this with .call
or .apply
which allow you to define context.
The problem is all solutions are brittle, tedious and (with Classes) add noise to the constructor.
Pass-by-ref and lack of Immutability
One of the trickiest parts I found when I started with JavaScript was its pass-by-reference approach. For example:
const myObj = { hasGoatee: false };
const myNewObj = myObj;
myNewObj.hasGoatee = true;
console.log(myObj);
// { hasGoatee: true }
The myNewObj
constant is now a reference to myObj
. This means both constants point to the same address space in memory.
This has a problem in that can lead to all sorts of bugs as its now possible to monkey-patch values onto myObj
and you can easily lose context or where certain data was set (or even what somethings value is).
On the other hand it is possible to do very fast comparisons with “referential equality” checks. e.g.
(myObj === myNewObj) // comparison of address space in memory
The best solution is to clone objects first if you are going to mutate them. e.g.
const myObj = { hasGoatee: false };
const myNewObj = Object.assign({}, myObj);
myNewObj.hasGoatee = true;
console.log(myObj);
// { hasGoatee: false }
This ensures that a new object has now been created (in a new address space) with the mutated property on, and the initial object is still available in its original state. This has the benefit that the original object is available to use or lookup if its required.
Unfortunately to compare contents you must do a “deep equality check” which will compare all object property values down its tree. Something which is only possible with a library (such as deep-equal) or to run manually, less performant with below:
JSON.stringify(object1) === JSON.stringify(object2);
The issue is that cloning
is a manual process and engineers have to be very careful to do it rather than pass-by-reference, something which can be easily missed.
On the other end of the spectrum to pass-by-ref is Immutability
. In a language with Immutable data types, a property is not able to be changed once created and to mutate it you MUST clone it first (often done for you). This allows object comparisons to become referential equality checks just checking memory address location rather than contents. Javascript can create an Immutable object withObject.freeze
however you still have to choose your comparison check carefully.
Certain languages enforce this by design and reap several benefits by doing so. One of which is that it fits in perfectly with the Functional programming paradigm as everything is pure and it is not possible to create some of these hard-to-find side-effects.
Overall I really enjoy using JavaScript, it has its faults but it is such a utility belt of functionality.
But with so many alternatives out there its worth getting real experience with others (for me its Python and Go) so you can make up your mind for yourself.
I know JavaScript Dragons are a very contentious area and I am sure there are many I have missed, so please leave a comment if there are any you feel are worth a mention.
Thanks for reading and please spare a clap if you liked this 🙏
More where this came from
This story is published in Noteworthy, where thousands come every day to learn about the people & ideas shaping the products we love.
Follow our publication to see more stories featured by the Journal team.