Objects
As introduced in the types section, any value that’s not of a primitive type (a string, a number, a boolean, a symbol, null or undefined) is an object . Even arrays or functions are, under the hoods, objects.
Here’s how we define an object:
const car = {
}
This is the object literal syntax, which is one of the nicest things in JavaScript.
You can also use the new Object
syntax:
const car = new Object()
Another syntax is to use Object.create()
:
const car = Object.create()
You can also initialize an object using the new
keyword before a function with a capital letter. This function serves as a constructor for that object. In there, we can initialize the arguments we receive as parameters, to setup the initial state of the object:
function Car(brand, model) {
this.brand = brand
this.model = model
}
We initialize a new object using
const myCar = new Car('Ford', 'Fiesta')
myCar.brand //'Ford'
myCar.model //'Fiesta'
Objects have properties . Every property has a name and a value.
You might think an object is basically a map , or dictionary , data structure, and you would be correct.
The value of a property can be of any type, which means that it can even be an object, as objects can nest other objects.
When a property value is a function, we call it method .
Objects can inherit their properties from other objects, and we’ll see this in details when we’ll talk about inheritance.
Objects are always passed by reference .
If you assign a variable the same value of another, if it’s a primitive type like a number or a string, they are passed by value:
let age = 36
let myAge = age
myAge = 37
age //36
const car = {
color: 'blue'
}
const anotherCar = car
anotherCar.color = 'yellow'
car.color //'yellow'
This is a key concept to understand, so play around with this.
Properties
I said that objects have properties, which are composed by a label associated with a value.
The object literal syntax we saw:
const car = {
}
lets us define properties like this:
const car = {
color: 'blue'
}
here we have a car
object with a property named color
, with value blue
.
Labels can be any string. Notice that I didn’t use quotes around color
, but if I wanted to include a character not valid as a variable name in the property name, I would have had to:
const car = {
color: 'blue',
'the color': 'blue'
}
This means spaces, hyphens, and more special characters.
As you see, we separate each property with a comma.
Retrieving the value of a property
We can retrieve the value of a property using 2 different syntaxes.
The first is dot notation :
car.color //'blue'
The second, which is mandatory for properties with invalid names, is to use square brackets:
car['the color'] //'blue'
If you access an unexisting property, you get undefined
:
car.brand //undefined
A nice way to check for a property value but default to a predefined value is to use the ||
operator:
const brand = car.brand || 'ford'
As said, objects can have nested objects as properties:
const car = {
brand: {
name: 'Ford'
},
color: 'blue'
}
You can access the brand name using
car.brand.name
or
car['brand']['name']
or even mixing:
car.brand['name']
car['brand'].name
Setting the value of a property
As you saw above you can set the value of a property when you define the object.
But you can always update it later on:
const car = {
color: 'blue'
}
car.color = 'yellow'
car['color'] = 'red'
And you can also add new properties to an object:
car.model = 'Fiesta'
car.model //'Fiesta'
How to remove a property
Given the object
const car = {
color: 'blue',
brand: 'Ford'
}
you can delete a property from this object using
delete car.brand
It works also expressed as:
delete car['brand']
delete car.brand
delete newCar['brand']
Setting a property to undefined
If you need to perform this operation in a very optimized way, for example, when you’re operating on a large number of objects in loops, another option is to set the property to undefined
.
Due to its nature, the performance of delete
is a lot slower than a simple reassignment to undefined
, more than 50x times slower.
However, keep in mind that the property is not deleted from the object. Its value is wiped, but it’s still there if you iterate the object:
Using delete
is still very fast, you should only look into this kind of performance issues if you have a very good reason to do so, otherwise it’s always preferred to have a more clear semantic and functionality.
Remove a property without mutating the object
If mutability is a concern, you can create a completely new object by copying all the properties from the old, except the one you want to remove:
const car = {
color: 'blue',
brand: 'Ford'
}
const prop = 'color'
const newCar = Object.keys(car).reduce((object, key) => {
if (key !== prop) {
object[key] = car[key]
}
return object
}, {})
How to count the number of properties in a JavaScript object
Use the Object.keys()
method, passing the object you want to inspect, to get an array of all the (own) enumerable properties of the object.
Then calculate the length of that array by checking the length
property:
const car = {
color: 'Blue',
brand: 'Ford',
model: 'Fiesta'
}
Object.keys(car).length
I said enumerable properties. This means their internal enumerable flag is set to true, which is the default. Check MDN for more info on this subject.
How to check if a JavaScript object property is undefined
In a JavaScript program, the correct way to check if an object property is undefined is to use the typeof
operator.
typeof
returns a string that tells the type of the operand. It is used without parentheses, passing it any value you want to check:
const list = []
const count = 2
typeof list //"object"
typeof count //"number"
typeof "test" //"string"
typeof color //"undefined"
If the value is not defined, typeof
returns the ‘undefined’ string .
Now suppose you have a car
object, with just one property:
const car = {
model: 'Fiesta'
}
This is how you check if the color
property is defined on this object:
if (typeof car.color === 'undefined') {
// color is undefined
}
Dynamic properties
When defining a property, its label can be an expression if wrapped in square brackets:
const car = {
['c' + 'o' + 'lor']: 'blue'
}
car.color //'blue'
Simpler syntax to include variables as object properties
Instead of doing
const something = 'y'
const x = {
something: something
}
you can do this simplified way:
const something = 'y'
const x = {
something
}
Methods
When used as object properties, functions are called methods:
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function() {
console.log(`Started`)
}
}
car.start()
Inside a method defined using a function() {}
syntax we have access to the current object by referencing this
:
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function() {
console.log(`Started ${this.brand} ${this.model}`)
}
}
car.start()
We don’t have access to this
if we use an arrow function:
const car = {
brand: 'Ford',
model: 'Fiesta',
start: () => {
console.log(`Started ${this.brand} ${this.model}`) //not going to work
}
}
car.start()
because arrow functions are not bound to the object. So, for object methods regular functions are the way to go.
Parameters
Methods can accept parameters, like regular functions:
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function(destination) {
console.log(`Going to ${destination}`)
}
}
car.start()
Dynamically select a method of an object in JavaScript
Sometimes you have an object and you need to call a method, or a different method, depending on some condition.
For example, you have a car
object and you either want to drive()
it or to park()
it, depending on the driver.sleepy
value.
If the driver has a sleepy level over 6, we need to park the car before it fells asleep while driving.
Here is how you achieve this with an if/else
condition:
if (driver.sleepy > 6) {
car.park()
} else {
car.drive()
}
Let’s rewrite this to be more dynamic.
We can use the ternary operator to dynamically choose the method name, get it as the string value.
Using square brackets we can select it from the object’s available methods:
car[driver.sleepy > 6 ? 'park' : 'drive']
With the above statement we get the method reference. We can directly invoke it by appending the parentheses:
car[driver.sleepy > 6 ? 'park' : 'drive']()
Prototypal Inheritance
JavaScript is quite unique in the popular programming languages landscape because of its usage of prototypal inheritance.
As we saw in the previous lesson, JavaScript tried to “fake” a more popular approach to inheritance. But under the hoods nothing changed from the past.
While most object-oriented languages use a class-based inheritance model, JavaScript is based on the prototype inheritance model .
What does this mean?
Every single JavaScript object has a property, called prototype
, which points to a different object.
This different object is the object prototype .
Our object uses that object prototype to inherit properties and methods.
Say you have an object created using the object literal syntax:
const car = {}
or one created with the new Object
syntax:
const car = new Object()
in any case, the prototype of car
is Object
:
If you initialize an array, which is an object:
const list = []
//or
const list = new Array()
the prototype is Array
.
You can verify this by checking with the Object.getPrototypeOf()
and the Object.prototype.isPrototypeOf()
methods:
const car = {}
const list = []
Object.getPrototypeOf(car) === Object.prototype
Object.prototype.isPrototypeOf(car)
Object.getPrototypeOf(list) === Array.prototype
Array.prototype.isPrototypeOf(list)
All the properties and methods of the prototype are available to the object that has that prototype:
Object.prototype
is the base prototype of all the objects:
Object.getPrototypeOf(Array.prototype) == Object.prototype
If you wonder what’s the prototype of the Object.prototype, there is no prototype: it’s null
. It’s a special snowflake .
The above example you saw is an example of the prototype chain at work.
I can make an object that extends Array and any object I instantiate using it, will have Array and Object in its prototype chain and inherit properties and methods from all the ancestors.
In addition to using the new
operator to create an object, or using the literals syntax for objects and arrays, you can instantiate an object using Object.create()
.
The first argument passed is the object used as prototype:
const car = Object.create({})
const list = Object.create(Array)
Pay attention because you can instantiate an array using
const list = Object.create(Array.prototype)
and in this case Array.isPrototypeOf(list)
is false, while Array.prototype.isPrototypeOf(list)
is true.
Classes
JavaScript has a quite uncommon way to implement inheritance: prototypal inheritance. We’ll see what that means later.
People coming from Java or Python or other languages had a hard time understanding the intricacies of prototypal inheritance, so the ECMAScript committee decided to sprinkle syntactic sugar on top of prototypical inheritance so that it resembles how class-based inheritance works in other popular implementations.
This is why, in 2015, the ECMAScript 6 (ES6) standard introduced classes.
This is important, it’s syntactic sugar: JavaScript under the hood didn’t change. It just added a nicer way to work with objects on top of the existing way.
A class definition
This is how a class looks.
class Person {
constructor(name) {
this.name = name
}
hello() {
return 'Hello, I am ' + this.name + '.'
}
}
A class has an identifier, which we can use to create new objects using new ClassIdentifier()
.
When the object is initialized, the constructor
method is called, with any parameters passed.
A class also has as many methods as it needs. In this case hello
is a method and can be called on all objects derived from this class:
const flavio = new Person('Flavio')
flavio.hello()
Static methods
Normally methods are defined on the instance, not on the class.
Static methods are executed on the class instead:
class Person {
static genericHello() {
return 'Hello'
}
}
Person.genericHello() //Hello
Class inheritance
Classes give JavaScript a more “classic” take on inheritance. Let’s describe it here, and then we’ll go into the “under the hoods” in the next lesson.
A class can extend another class, and objects initialized using that class inherit all the methods of both classes.
Inside a class, you can reference the parent class calling super()
.
If the inherited class has a method with the same name as one of the classes higher in the hierarchy, the closest method takes precedence:
class Programmer extends Person {
hello() {
return super.hello() + ' I am a programmer.'
}
}
const flavio = new Programmer('Flavio')
flavio.hello()
(the above program prints “ Hello, I am Flavio. I am a programmer. ”)
Classes do not have explicit class variable declarations, but you must initialize any variable in the constructor, although support for public class fields is in the works - and already in V8. JavaScript is always evolving.
Getters and setters
You can add methods prefixed with get
or set
to create a getter and setter, which are two different pieces of code that are executed based on what you are doing: accessing the variable, or modifying its value.
class Person {
constructor(name) {
this._name = name
}
set name(value) {
this._name = value
}
get name() {
return this._name
}
}
If you only have a getter, the property cannot be set, and any attempt at doing so will be ignored:
class Person {
constructor(name) {
this._name = name
}
get name() {
return this._name
}
}
If you only have a setter, you can change the value but not access it from the outside:
class Person {
constructor(name) {
this._name = name
}
set name(value) {
this._name = value
}
}
Getters and setters are very useful when you want to execute some code upon changing the property value, or if you want to create a “computed” property. You can alter the values you return by using a getter.
You can also run some code, like logging to the console or to a file when a value is changed.
Values and entries
Object.values()
This method returns an array containing all the object own property values.
Usage:
const person = { name: 'Fred', age: 87 }
Object.values(person) // ['Fred', 87]
Object.values()
also works with arrays:
const people = ['Fred', 'Tony']
Object.values(people) // ['Fred', 'Tony']
Object.entries()
This method returns an array containing all the object own properties, as an array of [key, value]
pairs.
Usage:
const person = { name: 'Fred', age: 87 }
Object.entries(person) // [['name', 'Fred'], ['age', 87]]
Object.entries()
also works with arrays:
const people = ['Fred', 'Tony']
Object.entries(people) // [['0', 'Fred'], ['1', 'Tony']]
Destructuring an object
Given an object, you can extract just some values and put them into named variables:
const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54, //made up
}
const {firstName: name, age} = person
name
and age
contain the desired values.
The syntax also works on arrays:
const a = [1,2,3,4,5]
const [first, second] = a
This statement creates 3 new variables by getting the items with index 0, 1, 4 from the array a
:
const [first, second, , , fifth] = a
Destructuring an object
Given an object, you can extract just some values and put them into named variables:
const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54, //made up
}
const {firstName: name, age} = person
name
and age
contain the desired values.
The syntax also works on arrays:
const a = [1,2,3,4,5]
const [first, second] = a
This statement creates 3 new variables by getting the items with index 0, 1, 4 from the array a
:
const [first, second, , , fifth] = a
Object.assign()
Object.assign()
performs a shallow copy of an object, not a deep clone.
const copied = Object.assign({}, original)
Being a shallow copy, values are cloned, and objects references are copied (not the objects themselves), so if you edit an object property in the original object, that’s modified also in the copied object, since the referenced inner object is the same:
const original = {
name: 'Fiesta',
car: {
color: 'blue'
}
}
const copied = Object.assign({}, original)
original.name = 'Focus'
original.car.color = 'yellow'
copied.name //Fiesta
copied.car.color //yellow
Using the Object Spread operator
This ES2015 feature provides a very convenient way to perform a shallow clone, equivalent to what Object.assign()
does.
const copied = { ...original }
Wrong solutions
Online you will find many suggestions. Here are some wrong ones:
Using Object.create()
Note: not recommended
const copied = Object.create(original)
This is wrong, it’s not performing any copy.
Instead, the original
object is being used as the prototype of copied
.
Apparently it works, but under the hoods it’s not:
const original = {
name: 'Fiesta'
}
const copied = Object.create(original)
copied.name //Fiesta
original.hasOwnProperty('name') //true
copied.hasOwnProperty('name') //false
JSON serialization
Note: not recommended
Some recommend transforming to JSON:
const cloned = JSON.parse(JSON.stringify(original))
but that has unexpected consequences.
By doing this you will lose any Javascript property that has no equivalent type in JSON, like Function
or Infinity
. Any property that’s assigned to undefined
will be ignored by JSON.stringify
, causing them to be missed on the cloned object.
Also, some objects are converted to strings, like Date objects for example, (also, not taking into account the timezone and defaulting to UTC), Set, Map and many others:
JSON.parse(
JSON.stringify({
a: new Date(),
b: NaN,
c: new Function(),
d: undefined,
e: function() {},
f: Number,
g: false,
h: Infinity
})
)
This only works if you do not have any inner objects and functions, but just values.
Merging objects
ES2015 introduced the Spread Operator , which is the perfect way to merge two simple objects into one:
const object1 = {
name: 'Flavio'
}
const object2 = {
age: 35
}
const object3 = {...object1, ...object2 }
If both objects have a property with the same name, then the second object property overwrites the first.
The best solution in this case is to use Lodash and its merge()
method, which will perform a deeper merge, recursively merging object properties and arrays.
See the documentation for it on the Lodash docs.
Quiz
Welcome to the quiz! Try to answer those questions, which cover the topics of this module.
You can also write the question/answer into the Discord chat, to make sure it’s correct - other students or Flavio will check it for you!
- tell the various ways you can use to initialize an object
- how can you set a property of an object? And how can you get its value?
- what is a method? What’s the difference between a method and a function?
- describe in your own words what is the prototype of an object
- do you find classes more intuitive to use?
- what is the useful thing about getters and setters?
- what’s the main difference between
Object.values()
andObject.entries()
? - describe what object destructuring means and what is the result of a destructuring operation
- shallow cloning vs deep cloning: what’s the difference?
- how can we merge 2 objects?