Dead Simple naive type checker for JavaScript based on constructor.name
Type Checking in JavaScript is very complicated for beginners. There are only Data Types. And everything the rest is something very different from well known and common used meaning of Types. We might be having null
or undefined
or something derived from Object
or other constructors. And any derived from Object is not a Type, but Structures. And this might seem very opinionated and dependent from JavaScript Engine, but undefined
has it's own Constructor, and when we make "void 0
" will invoke that constructor for new memory pointer marked as undefined
data. Therefore there is a lot of misunderstanding and misconceptions for those are new with JavaScript world. Sometimes it even harder for developers used to study with Strict Typing Systems in mind, but anyway, there are many pitfalls in general.
Each constructed structure has instance.constructor.name
, where instance
is a pointer to our variable and .constructor.name
is obligatory path, pointing us to the name of constructor invoked during instance creation. Therefore all non primitive so-called "types" has .constructor.name
. And what is "non primitive" then? Eh... non Data Types, but something else. And Data Types is that sort of thing which marks memory allocation as a type: data Primitives:
undefined
Boolean
String
Number
Symbol
BigInt
and not so about data, but primitive of everything wich we discuss more in a moment below:
null
And isn't it true that all these have constructors? Indeed, they do. But wich sort of constructor do they have? There is a bit difference: constructors for primitives do memory allocations for Data, and they then answers the question "which data type is that allocated memory".
It is not so special, but it is different from everything the rest in JavaScript: when other constructors are for Structures, not for Data.
And there is one very special thing: superheroic null
, which allows us to have all the other constructors. And din't null
has own constructor itself? As it is the only SuperDuperPuperClass, and Singleton we might say yes and no, simultaneously. And it is the only thing the other thing are derived from. It is so special and the same place so simple: but nobody cares of it, we just used to hate typeof null === 'object'
and that is it. But instead of doing this, please just look for the whole system. It is simple, straight, and it might be said: "it is Full".
Again: there are only Data Types and Structures, so what is the Next Step? Yes, there is a next step. And this step is instanceof
. When we construct something, we might be willing to check if we did construction from exactly that constructor in future. And this points us to the next big question in JavaScript: Inheritance. It might be the other even greater complicated topic, no matter how many times you've read about it. And from the other side there are not so many developers who was able to explain this topic well in each aspect. Remembering the early dates when there was a war between Netstcape Navigator and Internet Explorer 3~4 even I did two novelistic memoirs, please forgive me suggesting it:
- Romantic, with only one example: Architecture of Prototype Inheritance in JavaScript
- Nostalgic, with a lot of memories behind, few examples: Inheritance in JavaScript : Factory of Constructors with Prototype Chain : point of view from boring nerd
Starting from this is a moment I have to say sorry again, because now I will think we can share the same meanings.
Therefore we might succeed everything constructed has an instance.consructor.name
. But as we saw there is another big thing for Constructed: where constructible function meet Prototype being invoked with new
keyword. And we can rely on it, safely. The only exclusion is Object.create
, but even then, there was construction derived from prototype, though constructor was Object
itself. And we might be saying that {}
or []
have not yet being constructed? But there is no other way to simplify this process, so it was just lexer helping us with AST making special notes for messing up []
with new Array
and {}
with new Object
. And even deeper, as Arrays are derived from Objects, so by the typeof
checker Array
s are Object
s. This is done that way because typeof
just checks Data Types (primitives), and Array
is not Data Type, it is a special Structure of the other things. That is why it is an 'object'
for typeof
checker.
Being realistic we can see it is done that way for more than 20 years ago. And it is useless and pointless to think the other way for practical usage, despite we wish the "better". And we might don't like this, but there is true beauty when we accept and understand it instead of hating. It is very useful and very simple when we can rely it without breaking it. And this more than scalable and there are no good points for making it better. "This" system is Full, it is not necessary to extend it with something else.
As we then see: everything constructed (and derived from null) returns 'object' as a result of typeof
checking. And the special case null
returns object
too, because it points to the .constructor.name
and constructor of null
is Object
, the only thing being singleton null
has no properties. But truth is the following: thinking of data type checking there are no tasks of checking .constructor.name
from null
.
Keeping all this in mind we might build very simple system. Just being aware of the thing that does yet not implemented, this short sequence of conditional logic:
-
get the
typeof
of providedinstance
; -
if this is not an
'object'
, then it is a primitive, and we might return the answer right now; -
if it is
null
returnnull
; -
if
.consructor.name
returnsobject
, then it is structure, derived strictly fromObject
constructor, no matter how; -
if
.consructor.name
returns something from primitive constructors,'String'
or'Number'
or .., then the answer is that.name
, for better compatibility let.toLowerCase()
it; -
if
.consructor.name
is'Array'
,'Date'
,'Map'
,'WeakMap'
,'Set'
,'WeakSet'
we might again.toLowerCase()
the answer, because mostly this structures are used for storing Data; -
I'd personally suggest
'RegExp'
and'Promise'
as something very tightly coupled with data, but this is my choice, judge me an Issue; -
And yes, there are many types for Errors, and each one of them derives
Error
, so this is just an'error'
; -
The last thing is
'Function'
, and just because this is a very special case, there is a simple answer, we will use direct answer fromtypeof
received at1.
;
All the other types we might be willing to have in this "type checker" for our new shiny Type System will receive .constructor.name
as an automated Profit! It is not necessary to check them deeper. If we will wish we might rely on Object.getPrototypeOf(instance.constructor.name)
, and we might use underground names of constructors. If we will need it later, we might use instanceof
. But we should rely on our personal feelings about the other modules of our codebase because of the very broken thing comes with Object.create
's dawn in JavaScript ;^)
:
function foo() {}
var bar = { a: ‘a’};
foo.prototype = bar; // Object {a: "a"}
baz = Object.create(bar); // Object {a: "a"}
baz instanceof foo // true. oops.
Dear Eric, thank you very much for this example. Here is a link of Your wonderful article Common Misconceptions About Inheritance in JavaScript
So we will either avoid using Object.create
if we need that checks, or will invent something like hidden Symbol
, pointing us to real constructor.
Finally if should we bring very deep Prototype Chain to codebase, for example – Object->Array->Proxy->Object – then we might choose the last .constructor.name
as a pointer to the derived type. And for sure we might be willing additional typechecks in that case, but that is not so dead simple.
This library is a simple explanation of how everything works. It is very small, and we may rely on it for better understanding of JavaScript structures, especially Object
's derived structures. And, for sure, if we will use this understanding in a combination with other libraries, which does not provide the same level of abstraction in mind, we might fail. Or from other side we might win with distinguished explanation where they fail.