The Most Weird Parts of JavaScript
JavaScript is one of the most popular programming languages nowadays. Originally it was created in 10 days by Brendan Eich in 1995 for building simple scripts on the web pages but now it’s used for the development of different types of applications like sophisticated frontend or highload backend or even terminal tools - the one language to rule them all.
JavaScript evolves dramatically during the last 25 years. In 2020 it has a bunch of features that allow developers to write elegant code for solving everyday problems. JavaScript object model differs a lot from other languages like Java or Python. Some people in the community love JavaScript, but it would be futile to deny that the language has some weird moments. In this article, I’ll try to describe the top 3 things in JavaScript that are most weird for me.
3. Inheritance
JavaScript introduces the concept of objects as a collection of related data and functionality. Programs create and manipulate objects to perform different actions and solve software problems. One of the core concepts connected with objects is a prototypal inheritance.
The idea of prototypal inheritance is simple: every object has a prototype and when the program tries to receive one of the object’s properties and doesn’t find any value it tries to find this property in the object’s prototype.
const langs = ['JavaScript', 'Python', 'Java'];
console.log(langs.indexOf('Python')); // 1
In the example above object langs
has no indexOf
property but it has a prototype equals to
Array.prototype
which has such property. That is why this code works.
It’s possible to implement some functionality in the prototype and use it in all objects inherited from this prototype:
function Country(name, capital) {
this.name = name;
this.capital = capital;
}
Country.prototype.print = function() {
console.log(`Country ${this.name} with capital ${this.capital}`);
}
const Italy = new Country('Italy', 'Rome');
const Vietnam = new Country('Vietnam', 'Hanoi');
const Dominicana = new Country('Dominican Republic', 'Santo Domingo');
Italy.print();
Vietnam.print();
Dominicana.print();
All objects can use print
method because it’s defined in the prototype Country.prototype
.
The idea of prototypal inheritance is simple but the devil is in the detail.
Almost every object has Object.prototype
in its prototype chain and it can be mutated. This means
it’s possible to add or overwrite some behavior for almost all objects:
Object.prototype.sayHello = () => { console.log('Hello!') };
"test string".sayHello(); // Hello!
From the one side, such a feature allows you to implement polyfills (like core-js does) but from the other side, it can affect performance if you accidentally replace native code by the polyfill or even break down some code because you can’t predict who and when will change core objects behavior.
Another operator related to inheritance is instanceof
. It
allows to check whether an object has some other object in its prototype chain:
function Country(name, capital) {
this.name = name;
this.capital = capital;
}
const Italy = new Country('Italy', 'Rome');
console.log(Italy instanceof Country); // true
console.log(Italy instanceof Object); // true
In the code above instanceof
checks if Italy
object has Country.prototype
and
Object.prototype
in its prototype chain:
Italy.__proto__ === Country.prototype
Italy.__proto__ === Object.prototype
Italy.__proto__.__proto__ === Object.prototype
The tricky question, is it possible to evaluate such code as true?
(a instanceof b) && (b instanceof a);
At first sight, it seems impossible but instanceof
checks not objects a
and b
but
a.__proto__
and b.prototype
. After such an observation it’s possible to write such code:
const a = function() {};
const b = function() {};
a.__proto__ = b.prototype;
b.__proto__ = a.prototype;
console.log((a instanceof b) && (b instanceof a)); // true
The example above looks like a hack but it’s easy to meet such case in real life:
const a = Object;
const b = Function;
console.log((a instanceof b) && (b instanceof a)); // true
Almost every object in JavaScript has Object.prototype in it’s prototype chain, but at the same time Object is a constructor that is why it has Function.prototype in it’s prototype chain.
Moreover for some objects even such case is possible:
console.log(Object instanceof Object); // true
2. Equality operator (==)
As JavaScript is a dynamically typed language there is a problem of comparing values of different types. E.g. there is a text field and a script expects user to input number but the value of the text field is always a string. Developers can cast a value to a number manually or use an equality operator. Sometimes it works as expected but in some cases, the behavior can be counterintuitive:
'' == []; // true
[] == []; // false
[] == ![]; // true
This example looks strange when array equals to an empty string but not equals to another empty
array and even more strange when []
equals ![]
. Fortunately, this behavior is described in
detail in the
language spec or
MDN docs:
- it is said that if one operand is an object and another is a string JavaScript tries to convert
an object into a string using
valueOf
andtoString
methods. That is why'' == []
; - in case the operands are both objects equality operator returns true only if both operands
reference the same object. So
[] != []
, because there are two different objects; - in the last example
![]
converts the empty array to boolean valuetrue
, applies logicalNOT
and make itfalse
and then compares object and a boolean value. Logicalfalse
converted into0
and an empty array converted into0
, so0 == 0
.
Equality operator can be tricky that is why I prefer to perform necessary type conversions myself and use strict equality which has a simpler algorithm.
1. document.all
The most insane thing in JavaScript that blows my mind completely is document.all
. This property
is an HTMLAllCollection
rooted at the document node. It returns all of the document’s elements
accessible by order as in an array:
document.all[0]; // html node
document.all[1]; // head node
At the same time, it’s possible to use document.all
as a function to access elements by identifier
like document.getElementById
:
document.all('btn'); // equivalent to document.getElementById('btn')
Moreover document.all
is the only falsy object accessible to JavaScript:
console.log(typeof document.all); // undefined
console.log(document.all ? 1 : 0); // 0
This was done using [[IsHTMLDDA]] internal slot because of compatibility with older versions of Internet Explorer.
document.all
is a proprietary Microsoft extension to the W3C standard. It’s not recommended to use
but it still works in modern browsers like Firefox 78 or Google Chrome 83.
– Proprietary extension in a Microsoft’s browser. Take that off, what are you?
– Function, array, object, undefined.
Conclusion
When you start learning a new language there are a lot of weird things especially if the language evolved a lot during the time. If you can’t understand some behavior I suggest reading ECMAScript specification or at least developer.mozilla.org. Also, there is an interesting GitHub repo wtfjs which describes and explains a lot of tricky JavaScript examples.
There is a game called ReturnTrue. I don’t think everyone would like it but if you like competitive programming or want to understand JavaScript better it worth playing.