Things You didn’t Know About JavaScript Types
In the world of transpilers it’s hard to look back and return to the fundamentals, Typescript and Babel eliminated so many problems that JS devs had daily.
Types and equality rules of JavaScript were always confusing, and now many of those are abstracted away so you don’t have to worry about them, but that doesn’t mean that they completely disappeared.
In my experience, the best way to be great at something, is always to start from fundamentals and build up.
JavaScript has two types of values, there are different notations out there, but let’s call them Primitive and Complex. The biggest distinction between them is that primitives are always immutable, while complex values are mutable.
There is a popular myth in JS that everything is an object, we’ll explain primitives, see how they behave and why they are not an object. You can always check type of any value using typeof
operator.
Primitives
There are seven primitive types in JS, they are:
- Number
- BigInt
- String
- Boolean
- Symbol
- null
- undefined
You are probably familiar, and use daily Number, String and Boolean. These three are really self explanatory, interesting thing here is that immutability we mentioned, let’s consider this piece of code:
let name = 'Oskar';console.log(name[2]); // prints: 'k'// lets try to set name[2] to 'c'
name[2] = 'c';// the code above ran without errors so let's print name againconsole.log(name); // prints: Oskar// whoah name didn't change from original assignment
Look at the example above, even though you can run code like that without errors, String is a primitive value and primitives are immutable hence you can’t change name[2] = ‘c’
and name
variable will point to the same value as it was assigned to in the last assignment.
You can prohibit these assignments ( name[2] = ‘x’
) since they have no effect anyway, and many other sloppy things in JS, by using strict mode.
Note that you can always reassign variables, doing this name = 'Oscar'
will always work. You can always reassign if you need to, but you can’t mutate value itself, at least not for any of the primitive values.
BigInt
JavaScript is using floating point math which means that you can’t count on having every number in the world available to you, JS will always try to provide a number that’s pretty close to number you want, but there are no guarantees that you will get exact value you expect.
Probably most famous example of this is:
let num = 0.1 + 0.2;console.log(num === 0.3); // prints: false
What!!! (0.1 + 0.2) === 0.3 is false!!!
That’s right, open you browser console and try it out.
In JS floating point universe 0.3 doesn’t exist, so if you add two numbers above result will be:
console.log(0.1 + 0.2); // prints: 0.30000000000000004
That value (0.30000000000000004) is closest thing that that exists in JS universe to 0.3 , hence that’s returned when you do 0.1 + 0.2
You will not see these inconsistencies, between real world math and floating point math that JS uses, when you operate with whole numbers.
So 1 + 2 is always equal to 3, but only up to a certain point.
There is special constant Number.MAX_SAFE_INTEGER
, that has value of 9007199254740991, beyond that number even operating with whole numbers will not guarantee you correct results.
console.log(9007199254740991 + 2); // prints: 9007199254740992
Obviously the result above is not correct and that’s where BigInt comes into play, BigInt guaranties that no mater how large your numbers are, operations with them will always be correct.
You declare a number as BigInt by putting n
at the end of the number, and if we repeat same calculation again but now using BigInts:
console.log(9007199254740991n + 2n); // prints: 9007199254740993n
Now results are correct!
Symbol
Symbol is somewhat new (ES 2015) and it’s still very rarely used. They are not yet widely adopted so i’m not going to talk much about them.
You declare Symbols using:
let mySymbol = Symbol("mySymbol");
let mySymbol2 = Symbol("mySymbol");console.log(mySymbol === mySymbol2); // result: false
Symbols are always unique, hence above comparison being false.
You can use Symbols as an object property, so the main use case for them is to hide implementation. They are mostly used when you want to have private properties that you only use internally, and for public properties you can use String keys so anyone can access them.
null & undefined
Null and undefined represent missing values. Best practice convention is to use null when you want to intentionally say that value is not there, and undefined is used for unintentionally missing values.
This is not enforced by the code, it’s just a convention that most programmers use so they can distinguish these two values in a clear way.
Another use of undefined is as an implicit return value of a function, for any function in JavaScript that doesn’t have explicit return
statement, it is implied by the language that the last statement of that function is return undefined;
By a well known bug in C code in JS engine doing typeof null
incorrectly returns an 'object'
. This is a known issue, years old, but this can never be fixed because it would break millions of code bases around the world. There was a proposed fix in V8 engine but it was rejected for mentioned reasons, if you wan’t to read more check the official discussion.
As we are mostly done with primitive types lets see just one more time immutability in action.
let num = 1;// this code will run without error
num.customProperty = 1;console.log(num.customProperty); // prints: undefined
Again, above behavior is caused by all primitive types being immutable, so setting custom properties will not give an error, but it will never have an effect. Next we are looking at Complex types, they are mutable so they support adding any custom properties.
Objects and Functions
Objects and functions are Complex data types. They are mutable and support adding custom properties.
If you are wondering about arrays? Arrays are objects too, hence they are mutable and support everything that objects support.
let myArray = [1, '2', true];console.log(typeof myArray); // prints: 'object'//Objects are mutable
myArray[1] = 'Two';console.log(myArray); // prints: [1, "Two", true]// since arrays are objects you can give them properties
myArray.custom = 'custom'console.log(myArray.custom); // prints: 'custom'
Objects are created using curly braces.
let blogPost = {
author: 'Oskar',
publishedOn: 'Medium'
};// keys must be unique, but check is case sensitive
// note: this prop has lowercase 'o'
blogPost.publishedon = 'medium.com';console.log(blogPost);
// prints: { author: "Oskar",
publishedOn: "Medium",
publishedon: "medium.com" }// we made a mistake but objects are mutable,
// hence error can be corrected without creating new object
blogPost.publishedOn = 'medium.com';// delete unwanted prop
delete blogPost.publishedon;console.log(blogPost);
// prints: { author: "Oskar",
publishedOn: "medium.com" }
Functions, just as objects, are mutable so you can add or mutate their properties.
let myFunc = x => x + x;console.log(typeof myFunc); // prints: "function"myFunc.customProp = 'customProp';console.log(myFunc.customProp); // prints: 'customProp'console.log(myFunc(5)) // prints: 10
You might ask why would I add properties to functions, you likely never will, but it’s nice to know that you can. Just remember that functions and objects are mutable and things will make more sense.
I skipped things like Boolean, Numbers, { Object creation }, different ways of declaring functions and others because I believe they are well understood and very intuitive and I wanted to cover things that are confusing, not well understood, and not much talked about since those are things that produce bugs and give us headaches.
Also, covering all that in detail would make this text way too long and hardly readable, which is not my goal.
I took this approach to maximize possibility for you to learn something new, or something you never though about before or what you maybe have forgotten.
Hopefully my goal was successful!