Categories
JavaScript

Defining and Manipulating Classes and Objects in JavaScript

Spread the love

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.

Leave a Reply

Your email address will not be published. Required fields are marked *