In JavaScript, objects are entities that allow us to encapsulate data, functions, and other objects into one entity that we can access and manipulate. This is another fundamental building block of JavaScript programs.
Objects are used for modeling real-world data. An example would be a bird. We know that it has wings, a head, two legs, and two wings. Also, we know that it can chirp, eat, and fly.
We can model them in JavaScript by using an object. The body parts are the properties of the object, and the actions that it does are the methods.
Methods are functions that are part of the object. Properties are anything denoted with a key-value pair in JavaScript. Methods are special properties. When a property has a function as the value, it’s called a method.
For example, if we model a bird as an object, we can write:
const bird = {
numWings: 2,
numLegs: 2,
numHeads: 1,
fly(){},
chirp(){},
eat(){}
}
fly(){}
, chirp(){}
, and eat(){}
are methods and the rest are properties with data as values.
Creating Objects
In JavaScript, we can define objects in three ways. We can either define them as object literals, by using the object constructor method, or by using a class.
Object literal
Defining objects with object literals is straightforward. We just have to specify the properties and methods of an object directly. For example, we write:
const bird = {
name: 'Joe',
numWings: 2,
numLegs: 2,
numHeads: 1,
fly(){},
chirp(){},
eat(){}
}
To define an object as an object literal.
If we want to add more properties, we write:
bird.sleep = function(){};
We can assign anything as values of object properties, so strings, booleans, functions, arrays, and other objects all can be set as properties.
Object constructor
Equivalently, we can use the object constructor method to define an object. We can write:
let bird = new Object();
bird.name = 'Joe';
bird.numWings = 2;
bird.numLegs = 2;
bird.numHeads = 1;
This is a long way to define an object. It’s slower and harder to read than object literals.
Define object instances with classes
We can also define JavaScript by first defining a class, then using the new
keyword to create an object, which is an instance of the class.
To do this, we write:
class Bird {
constructor(name, numWings) {
this.name = name;
this.numWings = numWings;
}
logProperties() {
console.log(this)
}
}
const bird = new Bird('Joe', 2);
bird.logProperties();
We have the Bird
class, which is a template for creating an object with the name
and numWings
properties and the getProperties
method.
The Bird
class has a constructor
function that lets us pass in values for the properties and set it as properties of the object being created. It also has the logProperties
function.
this
is set as the object created as we aren’t using arrow functions as methods.
The object is defined by using the new
keyword and passing in the data into the constructor
. Once we’ve defined the object, we can call the methods defined inside it.
In this example, we called bird.logProperties
to log the value of this
, which should be {name: “Joe”, numWings: 2}
.
Prototypes
In JavaScript, prototypes are templates that let you create other objects. They can also be used for inheritance in JavaScript. So, if we have an Animal
object defined as:
function Animal(){
this.name = 'Joe'
}
We can extend it by creating a new object, like so:
let bird = animal.prototype;
bird.fly = function() {};
bird.chirp = function() {};
Then we get the fly
and chirp
methods, as well as the name
property in the bird
object.
Defining Functions in an Object
We can define a function in an object in a few ways.
We can use the function
keyword or arrow function as usual, but we can also write it with a shorthand for the function
keyword. For example, if we have a bird
object and we want to define the chirp
function, we can write:
const bird = {
chirp: function(){
console.log('chirp', this)
}
}
Or use the following shorthand:
const bird = {
chirp(){
console.log('chirp', this)
}
}
The two are the same as the chirp
function will have the bird
object as the value of this
.
On the other hand, if you use the arrow function:
const bird = {
chirp: () => {
console.log('chirp', this)
}
}
this
will be logged as the global window
object, as arrow functions do not change the value of this
to the object that the function is in.
Getting and Setting Object Properties
After an object is defined, it’s very useful to be able to get and set the properties of an object. There are two ways to get a property of an object. One is to use the dot notation, and another is the square bracket notation.
The dot notation allows us to get properties that have property names that follow the variable naming conventions. That means they start with a letter, underscore, or dollar sign and have no spaces or other special characters.
So, if we have:
const bird = {
name: 'Joe',
numWings: 2,
numLegs: 2,
numHeads: 1,
fly(){},
chirp(){},
eat(){}
}
Then, we can access the name
property by writing bird.name
.
The alternative syntax, which is the square bracket notation, can do the same thing as the dot notation, as well as access properties dynamically.
It also lets us access properties that are defined with names that don’t follow the variable name creation rules. To use the square bracket notation to get object properties, we write: bird['name']
. We can also write:
const prop = 'name';
bird[prop];
This allows us to get properties of an object by passing in variables and string, which is handy for dynamically modifying objects and using objects as dictionaries, as we can traverse the keys and modify them as well as the values.
To set object properties with both notations, we use the assignment operator, like so:
bird.name = 'Jane';
Or, with square bracket notation, we write:
bird['name']= 'Jane';
Get All the Top-Level Properties of an Object
All JavaScript has the following functions to get all the top-level properties of an object.
Object.keys
Object.keys
gets the top level list of keys of an object and returns an array of them. For example:
const a = {foo: 1, bar: 2};
const length = Object.keys(a).length // 2
You can call find
on the keys array to look for the key, like the following:
Object.keys(a).find(k => k == 'foo') // 'foo'
Object.getPropertyNames
Object.getPropertyNames
also gets a list of all top levels of keys of an object and returns them as an array. For example:
const a = {foo: 1, bar: 2};
const length = Object.getOwnPropertyNames(a).length // 2
You can call find
on the keys array to look for the key, like the following:
Object.getOwnPropertyNames(a).find(k => k == 'foo') // 'foo'
for…in Loop
There is also a special loop for looping through the keys of an object. You can do the following:
const a = {foo: 1, bar: 2};
let keysCount = 0;
for (let key in a) {
keysCount++;
}
console.log(keysCount) // 2
Checking If an Object Property Exists
hasOwnProperty
You can check if an object has a property by calling hasOwnProperty
of an object. For example:
const a = {foo: 1, bar: 2};
a.hasOwnProperty(key); // true
Deleting Properties of an Object
JavaScript has the delete
operator to remove a property of an object. If we have:
const bird = {
name: 'Joe',
numWings: 2,
numLegs: 2,
numHeads: 1,
fly(){},
chirp(){},
eat(){}
}
Then we run delete bird.name
, then the bird.name
property will be removed. When you log it, it will log as undefined
.
JavaScript Inheritance
In JavaScript, we can create classes where the properties can be included in the properties of a child class.
So, we can have a high-level class that contains the properties that are common to all the child classes, and the child class can have its own special properties that are not in any other classes.
For example, if we have an Animal
class with the common properties and methods, like name
and the eat
method, then the Bird
class can just inherit the common properties in the Animal
class. They don’t have to be defined in the Bird
class again.
We can write the following to do inheritance in JavaScript:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log('eat');
}
}
class Bird extends Animal {
constructor(name, numWings) {
super(name);
this.numWings = numWings;
}
}
const bird = new Bird('Joe', 2);
console.log(bird.name)
bird.eat();
In the example above, we have the parent class, Animal
, that has the eat
method, which all classes that extends
from Animal
will have, so they don’t have to define eat
again.
We have the Bird
class which extends the Animal
class. Note that in the constructor
of the Bird
class, we have the super()
function call to call the parent’s class constructor to populate the properties of the parent class in addition to the properties of the child class.
this
Keyword
The this
keyword allows us to access the current object’s properties inside an object unless you’re using arrow functions.
As we can see from the above example, we can get the properties of the instance of the child and the parent class in the object.
Copying Objects
Copying objects means making a new object reference to an object that has the same contents as the original.
It is used a lot to prevent modifying the original data while you assign a variable to another variable. Because if you assign a variable to a new one, the new one has the same reference as the original object.
There are a few ways to clone objects with JavaScript. Some functions do shallow copying, which means that not all levels of the object are copied, so they may still hold a reference to the original object.
A deep copy copies everything so that nothing references the original object, eliminating any confusion which arises from a shallow copy.
If you assign an object to another variable, it just assigns the reference to the original object, so both variables will point to the original object.
When one of the variables is manipulated, both will be updated. This is not always the desired behavior. To avoid this, you need to copy an object from one variable to another.
Shallow copy
In JavaScript, this is easy to do. To shallow copy an object, we can use Objec.assign()
, which is built into the latest versions of JavaScript.
This function does a shallow copy, which means it only copies the top level of an object, while the deeper levels remain linked to the original object reference. This may not be desired if there are nested ones in your original object.
Here is an example of how to use Object.assign
:
const a = { foo: {bar: 1 }}
const b = Object.assign({}, a) // get a clone of a which you can change with out modifying a itself
You can also clone an array like this:
const a = [1,2,3]
const b = Object.assign([], a) // get a clone of a which you can change with out modifying a itself
Deep copy
To do a deep copy of an object without a library, you can JSON.stringify
then JSON.parse
:
const a = { foo: {bar: 1, {baz: 2}}
const b = JSON.parse(JSON.strinfy(a)) // get a clone of a which you can change without modifying a itself
This does a deep copy of an object, which means all levels of an object are cloned instead of referencing the original object.
JSON.parse
and JSON.stringify
only work with plain objects, which means they cannot have functions and other code that runs.
We can also use object destructuring to shallow clone objects, like so:
const a = { foo: {bar: 1}}
const b = {...a} // get a clone of a which you can change with out modifying a itself
Now that we know how to create objects, we can easily store and manipulate data. We can create programs that do non-trivial things with the use of objects and classes.