A JavaScript Refresher Tutorial: Objects

Introduction

In the first part of this tutorial, we had a brief overview of objects in JavaScript. However, in this blog post, we’re going to cover this concept in much more detail.

In programming, the term ‘object’ is widely used especially when it comes to the object-oriented programming (OOP) paradigm. In fact, if we would like to define it in this context, I don’t think that we could find a better explanation than Steve Jobs’s one. In an excerpt from a 1994 Rolling Stone interview, Jobs explains what objects are.

Jeff Goodell: Would you explain, in simple terms, exactly what object-oriented software is?

Steve Jobs: Objects are like people. They’re living, breathing things that have knowledge inside them about how to do things and have memory inside them so they can remember things. And rather than interacting with them at a very low level, you interact with them at a very high level of abstraction, like we’re doing right here.

Here’s an example: If I’m your laundry object, you can give me your dirty clothes and send me a message that says, “Can you get my clothes laundered, please.” I happen to know where the best laundry place in San Francisco is. And I speak English, and I have dollars in my pockets. So I go out and hail a taxicab and tell the driver to take me to this place in San Francisco. I go get your clothes laundered, I jump back in the cab, I get back here. I give you your clean clothes and say, “Here are your clean clothes.”

You have no idea how I did that. You have no knowledge of the laundry place. Maybe you speak French, and you can’t even hail a taxi. You can’t pay for one, you don’t have dollars in your pocket. Yet I knew how to do all of that. And you didn’t have to know any of it. All that complexity was hidden inside of me, and we were able to interact at a very high level of abstraction. That’s what objects are. They encapsulate complexity, and the interfaces to that complexity are high level.

So, to sum up, an object is an entity which has a specific identity, holds a state at a particular point in time, and behaves in a certain manner.

In JavaScript, an object is a collection of properties, or let’s say a collection of key-value pairs. Usually, when the value of a property is a function, we call it a method.

Creating Objects in JavaScript

To create an object in JavaScript, you can use one of the three following ways,

  • Using the literal notation
  • Using the constructor function
  • Using the Object.create() method
Using the Object Literal Notation

To create an object using the literal expression, use the curly brackets { } as in the following example,

// Creating an object
const randomObj = {
  somePropertyName : 'foo',
  _bar : 'bar',
  123 : {},
  '':'An empty string as property name',
  'I am a function' : function(){
    console.log('hi');
  }
}

// Accessing properties
console.log(randomObj.somePropertyName); // output : 'foo'
console.log(randomObj._bar); // output : 'bar'
console.log(randomObj[123]); // output : {}
console.log(randomObj['']); // output : 'An empty string as property name'
randomObj['I am a function'](); // output : 'hi'
randomObj.123 // output: SyntaxError!
randomObj.I am a function // output: SyntaxError!

Note that for the key (before the colons), you can set a name, a number, or a string. Basically, you can use, as a key, whatever data type can be converted into a string. On the other hand, for the properties’ values, you can set any data type you want, including objects.

If you use a name as a key, it must start with a letter, underscore (_), or a dollar sign ($); subsequent characters can also be digits (0-9). Yet, you should take into consideration that it’s case-sensitive.

Don’t forget to add a comma (,) at the end of each key-value pair othewise you’ll get a syntax error.

Now to access a property, use the dot notation (.) as in lines 13 and 14 if you’re using a valid name as a key. On the other hand, use the square bracket notation ([]) otherwise (See lines 15,16, and 17). In fact, notice that in lines 18 and 19 we’ve tried to access properties with invalid names using the dot notation. Therefore, we had a syntax error.

When you use an object literal expression as in the example above, JavaScript is going to create an instance of Object for you. It means that it’s going to execute “new Object()” implicitly behind the scenes.

Using the Constructor Function

The other alternative you can use to create objects in JavaScript is the constructor function. In opposition to the object literal notation method, here you’re meant to use the new operator explicitly to create an instance of your object. But before that, you should first define the object using the constructor function.

Let’s see the following example,

// Pet's object definition
function Pet(name, age, type){
  //properties
  this.name = name;
  this.age = age;
  this.type = type;
  this['random prop'] = 'random'; // Makes no sense I know ;)
  
  //methods
  this.sayWhoAmI = function(){
    console.log(`Hey! I am a ${this.type}. My name is ${this.name}, and I am ${this.age} years-old.`);
  }
}

// Creating instances
const myCat = new Pet('Tiger', 1, 'cat');
const myFriendsDog = new Pet('Appolo', 3, 'dog');

// Accessing properties and methods
myCat.sayWhoAmI(); // Hey! I am a cat. My name is Tiger, and I am 1 years-old.
console.log(myCat['random prop']); // 'random'
myFriendsDog.sayWhoAmI(); //Hey! I am a dog. My name is Appolo, and I am 3 years-old.

In the example above, we’ve defined an object Pet using a constructor function. Note that by convention the name of the function starts with a capital letter. We use “this” to assign values to the object’s properties via the constructor function parameters. In our case, we’re passing the name, age, and type of the pet. In fact, “this” refers to the current object being created.

In lines 15 and 16, we’ve created instances myCat, and myFriendsDog of the Pet object. For that, we’ve used the new operator followed by the constructor function invocation with the necessary arguments.

Lines 7 and 21 are there to tell you that for keys (properties and methods names), same naming rules applies as we’ve seen in the object literal notation method section. If the key ain’t a valid name, just use the square bracket notation.

Using the Object.create() method

Sometimes, you might need to create a new object from another object instance. In this case, you can use the Object.create() method without the need of defining it with the constructor function.

Let’s have a look at the following example,

// creating the object rectangle1
const rectangle1 = {
  width : 10,
  height : 5
}

// creating another instance of rectangle1
const rectangle2 = Object.create(rectangle1);

// accessing props
console.log(rectangle1.width === rectangle2.width); // true
rectangle2.height = 7;
console.log(rectangle1.height === rectangle2.height); // false

So, we’ve created another instance rectangle2 taking the rectangle1 as a prototype for it. We’ve used the Object.create() method to create an instance instead of using the new operator.

Line 11, proves that rectangle2 was an identical copy of rectangle1. However, we changed it in line 12 to make it different from it.

Computed Properties

If you’re creating an object using the literal syntax method, then you might take advantage of the “computed properties” feature. It allows you to add a property with a dynamic key. Let’s have a look at the following example for more illustration.

const prop = 'greeting expression';
const obj = {
  [1+2+4] : 'seven',
  [2*3] : 'six',
  [prop] : 'Hello there!'
};

console.log(obj); // output: {7: 'seven', 6: 'six', greeting expression: 'Hello there!'}

What we’re doing here in lines 3,4 and 5 is adding dynamic keys. So, the code between the square brackets is going to be evaluated first.

For example, in our case, “1+2+4” evaluates to “7”. Therefore, the key is going to be “7” as shown in the output in line 8. The same goes for line 4. In line 5, we’re using the variable prop as the key name. Thus, it evaluates to ‘greeting expression’ first before it’s used.

Getters and Setters

Getters are some kinds of special methods that allow you to access a property value which is dynamically computed. In the same way, setters allow you to set a dynamically computed value to a property. Ok, what do we mean by that?

How to Use Them

Let’s have a look at the following example,

const identity = {
  
  idNumber: '1234',
  name: 'ab',
  
  get identifier() {
    return this.name + '#' + this.idNumber;
  },
  set identifier(value){
    const parts =  value.split('#');
    this.name = parts[0];
    this.idNumber = parts[1];
  }
  
}
console.log(identity.identifier); // get identifier() is called ==> ab#1234
identity.identifier = "cd#12323444"; // set identifier() is called ==> the new values are set
console.log(identity.identifier); // get identifier() is called => cd#12323444

So, lines 16, 17, and 18 above illustrate that we’re accessing (retrieving and updating) the property identifier with the simple dot notation syntax. Nevertheless, there are some things happening behind the scenes as the getter and setter methods for identifier property are called implicitly.

Note that the getter method always has zero parameters. Whereas, the setter method accepts only one parameter which is the new value to set.

Why Use Them

Now the question that might arise is why would you use getters and setters in the first place? I’ve asked myself this question a lot, I made some investigations on it, and that’s what resonated with me…

First, I want to share with you this quote from MDN documentation,

Getters give you a way to define a property of an object, but they do not calculate the property’s value until it is accessed. A getter defers the cost of calculating the value until the value is needed. If it is never needed, you never pay the cost.

So, what we can understand from this is that if the calculation of a property is too expensive (consumes a lot of resources), then we might consider using getters.

Second, look at this piece of code and let’s imagine the following scenario,

const person = {
  name: "ab"
}

// printing the person's name 10000 times in 10000 diffrent places in our code....
console.log(person.name);
console.log(person.name);
console.log(person.name);
console.log(person.name);
console.log(person.name);
console.log(person.name);
//....
//..
//.

One morning, your boss got up on the wrong side of the bed and asked you: “Please, I want you to print the person’s name all-capitalized in every place. I want that done in one hour from now!”

So, the first obvious solution that you might have thought of is changing every

console.log(person.name);

into

console.log(person.name.toUpperCase());

In this case, you would have got 10000 lines to change in 10000 different places in your code. Yet, this process would be time-consuming and tyring…

However, because you know about getters/setters in JavaScript…You went through this challenge smartly and took advantage of them.

const person = {
  notFormattedName: "ab", // change 1
  // change 2
  get name(){ 
    return this.notFormattedName.toUpperCase();
  }
}

// printing the person's name 10000 times in 10000 diffrent places in our code....
console.log(person.name);
console.log(person.name);
console.log(person.name);
console.log(person.name);
console.log(person.name);
console.log(person.name);
//....
//..
//.

So, you end up making only two changes in your code. You, first, added a new property notFormattedName which is going to hold the initial non-formatted value of the person’s name. And second, you converted the old name property into a getter to do some formatting behind the scenes.

Comparing Objects

In one of my previous articles on core JavaScript basics, we’ve seen how to use comparison operators. However, the examples used for illustration were only around comparing primitive data types.

Still, for the sake of explaining how comparing objects works, I’d like to start by showing you again a simple example of comparing primitives.

const a = 'A';
const b = 'A';

console.log(a===b); // true;

What is really happening in lines 1 and 2 behind the scenes is that memory is allocated to each variable and given the actually assigned value to each one of them. Therefore, when comparing the two variables as in line 4, we get true since the two variables are holding the same value and are of the same type.

Now let’s replicate the same example using non-primitive types (objects),

const a = {
  prop1: 'A',
  prop2: 'B',
};
const b = {
  prop1: 'A',
  prop2: 'B',
};

console.log(a===b); // false;

In line 10, comparing the variables a and b returns false despite the fact that they are assigned an identical value. Why is it the case?

Objects are reference types. It means that when we assign an object to a variable, the variable is going to hold a reference (the location) to that object in memory. In other words, it is going to carry “where the object is in memory“, not “what the object has“. So, distinct objects are never equal (referential equality) even though they have the same properties.

Now, you might be asking yourself how can you compare objects (their actual properties’ values) in JavaScript.

In fact, there are different ways to achieve this. However, in this article, I am only going to highlight some of them without getting too much into detail.

Shallow Comparison

Create a function that is going to extract the list of properties of both objects, then compare the properties’ values for equality. This method would work fine if the compared objects don’t contain nested objects (both objects’ properties’ values are only primitives). Because remember, “distinct objects are never equal even though they have the same properties“.

Using JSON.stringify()

You can use the JSON.stringify() method to convert both objects and compare them as strings. Yet, this solution won’t work if the objects’ properties order is changed, or if the objects contain methods.

Deep Comparison

In the deep comparison, you would take the same function you made on the shallow comparison, and empower it with recursivity. As a result, you’ll give it the ability to iterate also over nested objects’ properties’ values to check for equality.

By the way, for deep comparison, you could consider these two ready-to-use utilities: isDeepStrictEqual(val1,val2) of Node.js, or _.isEqual(value, other) of the lodash library.

Inheritance

Inheritance in JavaScript is possible through one special mechanism in JavaScript called the prototype chain. All objects in JavaScript inherit at least from one other object. What do mean by that?

Let’s have a look at this example,

const a = {
    prop : 'prop'
}

console.log(a);

If you open the JavaScript console on your browser and try out this piece of code, you’ll get something like that,

Note that there’s a special property [[Prototype]] that you can’t access with the dot notation. This special property is the object from which our newly created object “a” inherits. In our case, it’s the built-in object Object.

Our new kid “a” has got some superpowers now. It can access properties that were initially defined in the object Object’s prototype property. Therefore, something like “a.toString()” is valid.

If this sounds still a bit confusing, I highly recommend that you read this article where I explain prototype in JavaScript extensively.

Deleting properties

You can only delete properties that are non-inherited. To do it, use the delete keyword.

const a = {
  prop1: 1,
  prop2: 2,
};

delete a.prop1; // removing prop2
console.log(a); // { prop2: 2 }

As you can see in line 7, it outputs the object without the property prop1 since we deleted it in line 6.

That’s it for this second part of the JavaScript refresher tutorial.

If you have any questions or feedback, please, refer to the comments section below.

See you in the next part of this tutorial.

Enjoy 🙂

Leave a Reply

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