DEV Community

Rafael Leitão
Rafael Leitão

Posted on

Back to the basics: Primitive and Object types in Javascript

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:

  1. string
  2. number
  3. bigint
  4. boolean
  5. undefined
  6. null
  7. symbol
  8. 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


Enter fullscreen mode Exit fullscreen mode

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:

Alt Text

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.

Alt Text

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" };


Enter fullscreen mode Exit fullscreen mode

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:

Alt Text

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:

Alt Text

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


Enter fullscreen mode Exit fullscreen mode

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"


Enter fullscreen mode Exit fullscreen mode

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"


Enter fullscreen mode Exit fullscreen mode

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"]


Enter fullscreen mode Exit fullscreen mode

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"]


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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" }


Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
dev_003c8536bbc481b profile image
Dev • Edited
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"]
Enter fullscreen mode Exit fullscreen mode

For anyone reading this, this is completely wrong.
cars and newCars 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 the cars variable. newCars remains unchanged and still points to the original array in memory.

So the final console.log statements will actually print:

console.log(cars) // ["tesla", "audi"]
console.log(newCars) // ["tesla"]
Enter fullscreen mode Exit fullscreen mode

Image description

Collapse
 
arjun27sharma profile image
Arjun Sharma • Edited

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"]

Image description
output :
Image description

Using cars.push("audi")

Image description
output:
Image description