Hello everyone đź‘‹
I was watching the video Object-oriented Programming in JavaScript: Made Super Simple | Mosh from Mosh Hamedani and I thought it may be nice to share what I'm learning/reviewing. That's why I'm planning a series of posts to cover some concepts as a way to learn more about the topics and hopefully help some people as well.
In this article I will cover Javascript Types and their differences.
Javascript Types
There are eight data types in Javascript:
- string
- number
- bigint
- boolean
- undefined
- null
- symbol
- Object
The first 7 of them are commonly called Primitive Types and everything else are Object Types.
Primitive Types
They can only store a single data, have no methods and are immutable.
Wait, How come? They are mutable... Actually, they are not. We usually confuse the primitive value itself with the variable we assign the primitive value. See below:
// We cannot mutate the string
let car = "car"
console.log(car) // car
car.toUpperCase()
console.log(car) // car
car[0] = "b"
console.log(car) // car
// But we can assign a new value to the same variable
car = car.toUpperCase()
console.log(car) // CAR
The variable can be reassigned a new value but the existing value of the primitive cannot be changed like we do with arrays or objects.
So this is one of the main differences between both types:
Primitive Types are immutable and Object Types are mutable.
Aah, okay. Got it! But what about that they don't have methods if you've just used one?
That's another interesting point! Primitive Types
have no methods but, except for null
and undefined
, they all have object equivalents that wrap the primitive values then we're able to use methods.
For string
primitive there is String
object, for number
primitive there is Number
, and so there are Boolean
, BigInt
and Symbol
.
Javascript automatically converts the primitives to their corresponding objects when a method is to be invoked. Javascript wraps the primitive and call the method.
See below how a String
object is with its primitive value and __proto__
(which is beyond our scope here but it's related to its object prototype) with the associated methods:
That's how we can access properties like length
and methods like indexOf
and substring
when working with string
primitives.
When Javascript wraps them with their corresponding objects, it calls the valueOf
method to convert the object back to the primitive value when Javascript finds an object where a primitive value is expected.
Object Types
Differently from the primitives, Objects can store collections of data, their properties, and are mutable.
// We can mutate objects without needing to reassign the variable
let cars = ["bmw", "toyota"]
console.log(cars) // ["bmw", "toyota"]
cars.push("tesla")
console.log(cars) // ["bmw", "toyota", "tesla"]
let car = { brand: "tesla" }
car.year = 2021
console.log(car) // { brand: "tesla", year: "2021" };
Examples of Object
types are Array and Object. Different from Primitive Types
they have built-in methods. You can see below how an array and object is shown here on the browser with some of their methods:
As crazy as it seems, functions
are actually objects too, they are Function
objects, which are callable.
Just to illustrate that and for curiosity, see how functions could also be created:
This is just for educational purpose since it is not recommended to use it like this and there are problems with closures as shown here.
Okay, we learned a bit more about these types so let’s see some of the differences when working with them.
Differences between types
1. Assigning to a variable and copying value
The difference in the way the values are stored in variables is what makes people usually call Object Types
as Reference Types
.
Primitive Types
When we assign a primitive type to a variable, we can think of that variable as containing that primitive value.
let car = "tesla"
let year = 2021
// Variable - Value
// car - "tesla"
// year - 2021
So when we assign this variable to another variable, we are copying that value to the new variable. Thus, primitive types are "copied by value".
let car = "tesla"
let newCar = car
// Variable - Value
// car - "tesla"
// newCar - "tesla"
Since we copied the primitive values directly, both variables are separate and if we change one we don't affect the other.
let car = "tesla"
let newCar = car
car = "audi"
// Variable - Value
// car - "audi"
// newCar - "tesla"
Object Types
With Object Types
things are different. When we assign an object to a variable, the variable is given a reference to that value. This reference stores the address
to the location of that value in memory(techically more than that but let's simplify). So the variable doesn't has the value itself.
Let's imagine the variable, the value it stores, the address in memory and the object in the comming snippets:
let cars = ["tesla"]
// Variable - Value - Address - Object
// cars - <#001> (The reference) - #001 - ["tesla"]
This way, when we assign this variable to another one we are giving it the reference for the object and not copying the object itself like it happens with the primitive value. Thus, objects types are "copied by reference".
let cars = ["tesla"]
let newCars = cars
// Variable - Value - Address - Object
// cars - <#001> (The reference) - #001 - ["tesla"]
// newCars - <#001> (The reference stores the same address)
cars = ["tesla", "audi"]
// Variable - Value - Address - Object
// cars - <#001> (The reference) - #001 - ["tesla", "audi"]
// newCars - <#001> (The reference stores the same address)
console.log(cars) // ["tesla", "audi"]
console.log(newCars) // ["tesla", "audi"]
Both of them have references to the same array object. So when we modify the object from one of the variables the other will also have this change.
2. Comparison
Understanding the differences of what is stored in variables when dealing with primitive and object types is crucial to understand how we can compare them.
Primitive Types
Using the strict comparison operator ===
, if we compare two variables storing primitive values they are equal if they have the same value.
let year = 2021
let newYear = 2021
console.log(year === 2021) // True
console.log(year === newYear) // True
However, if we compare two variables which were assined Object Types
, we are actually comparin two references instead of their objects. So they are equal only if they reference the exactly same object.
let cars = ["tesla"]
let newCars = ["tesla"]
console.log(cars === newCars) // False
console.log(cars === ["tesla"]) // False
// Now we copy the reference of cars to newCars
newCars = cars
console.log(cars === newCars) // True
Even though, in the beginning of the code snippet we were working with the same content in the arrays the variables didn't have the same references, they had references to different array objects in memory. However, after we copied the reference to newCars
, since now they are "pointing" to the same object the evaluation is True
.
So, to compare objects we cannot simply use the ===
operator because even though they might have the same properties they may not reference the same object. There are some ways to do that and so I'd recommend you to read this article.
3. Passing to functions
When we pass primitive or object types to functions it's like we are copying their values/references to the functions parameters as if we were assigning them with =
.
Since we have seen that when we assign them to new variables we are either copying their value(for primitive types) or reference(for object types), it's easier to understand what happens with functions and their outside scope.
Primitive Types
When we are passing Primitive Types
to functions we're copying their values to the functions parameters so it doesn't affect the initial variable in the outside scope.
let year = 2021
function getYearWithoutCovid (freeYear) {
freeYear = 2022
return freeYear
}
const newYear = getYearWithoutCovid(year)
console.log(year) // 2021
console.log(newYear) // 2022
Passing year
to the function, we are copying its value to the function parameter(freeYear
will be 2021) so the original variable is not affected.
Object Types
With Object Types
, we are copyin their references when passing them as functions parameters. So, if we change the object inside the function this will also be seen in the outside scope.
let person = { name: "Paul", status: "unemployeed" }
function getAJob (person) {
person.status = "employeed"
return person
}
const newPerson = getAJob(person)
console.log(person) // { name: "Paul", status: "employeed" }
console.log(newPerson) // { name: "Paul", status: "employeed" }
When we pass person
to the function, we are copying its reference to the function parameter, not its object value. Changing it inside the function will affect the initial object in the outside scope since both variables have references to the same object.
That's why its recommended to use Pure Functions
in this case (which are not in the scope of this article but I encourage you to search about it <3). We then create a local copy of that person
inside the function and modify it instead of the object passed.
Conclusion
I hope that with this article you could understand a little bit more about the data types in Javascript and could also learn the key differences between them.
I just tried to share what I learned while reviewing these concepts so there are more things to add but I thought this was an educational way to explain. If you have things to add and discuss leave a comment :) If it helped you somehow leave a heart <3
Also, follow me on Twitter if you want, might share nice things there too :)
References
https://262.ecma-international.org/11.0/#sec-ecmascript-data-types-and-values
https://flaviocopes.com/difference-primitive-types-objects/
https://dmitripavlutin.com/value-vs-reference-javascript
https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0
https://codeburst.io/javascript-essentials-types-data-structures-3ac039f9877b#01e0
https://mattgreer.dev/blog/javascript-is-a-pass-by-value-language/
https://developer.mozilla.org/en-US/docs/Glossary/Primitive
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf
Top comments (2)
For anyone reading this, this is completely wrong.
cars
andnewCars
initially hold a reference to the same array in memory. This means that any mutating functions called on this array object will indeed affect both variables.But the moment
cars
is set to["tesla", "audi"]
it creates a new array in memory and sets the associated pointer to thecars
variable.newCars
remains unchanged and still points to the original array in memory.So the final
console.log
statements will actually print:yes that is true, instead of writing
cars = ["tesla", "audi"]
it should have been
cars.push("audi")
because cars = [] will create a new object so reference changes
Using cars = ["tesla", "audi"]
output :
Using cars.push("audi")
output: