Creating and Using Objects

Overview

An object in JavaScript is a named variable that has a non-primitive data type. In fact, just about anything in JavaScript that isn’t a primitive data type is an object. We can think of it as an unordered collection of properties, each of which is a key-value pair. Each property has a name (the key), and a value that is associated with that name.

The property name can be anything you like - there are no restrictions on naming properties - although you should aim for something that is both meaningful and reasonably concise. Property values can be primitive values, other objects, or functions. If an object property is a function (as opposed to a primitive value, object, or object reference) we tend to refer to it as a method rather than as a property. We can think of a JavaScript object as a collection of related properties and methods.

In the following sections, we will explore the various ways in which objects and their properties can be created and used. Before we proceed, note that JavaScript objects should not be confused with classes, which were introduced into the JavaScript language for the first time (rather unnecessarily according to some sources) in the ECMAScript 2015 (ES6) language specification. As is the case with other object-oriented languages like C, C++ and Java, JavaScript classes are templates for JavaScript objects, not objects in their own right. We will be looking at JavaScript classes in a separate article.

JavaScript also has a number of built-in objects, each of which has their own collection of properties and methods. We’ll be looking at JavaScript’s built-in objects elsewhere. In this article, we are concerned primarily with user-defined JavaScript objects that can be created as stand-alone entities, without invoking a class. Having said that, a JavaScript object can inherit properties and methods from another object, which is referred to in this context as the object’s prototype. We will be exploring the concept of inheritance in due course.

Objects are the cornerstone of the JavaScript language. They allow us to model both abstract constructs and real-world objects, and they act as containers for a collection of data items that describe those objects. They also allow us to define methods to add, delete and update those data items, all in one dedicated package.

Creating an object

There are several ways to create a JavaScript object. One quick and easy way to create an object is by using the object literal syntax. This syntax creates an object and initialises it at the same time. In order to demonstrate, we’ll create a simple object to represent a person:

const person = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: [1955,2,25]
}; // this is an object

let dob = new Date(person.dateOfBirth);
let dobString = dob.toString().substring(4,15);
console.log(person.firstName, person.lastName, dobString);
// John Doe Feb 25 1955

We have used the object literal syntax here. The object person is declared using the const keyword. We initialise the object by declaring three properties and assigning them values inside curly braces, immediately following the object name. The properties are declared as comma-separated key-value pairs.

Note that we could also use the keywords var or let to declare the object. Before the let and const keywords were introduced in the ES6 language specification published in 2015, we would have used var. This is now frowned upon because any variables declared with var outside of a function have global scope, which means they can be accessed from any part of a script or program, which not always desirable.

It is also possible to redefine a variable declared with var. This can be dangerous, especially if we have a large number of variables in a program, since we could inadvertently re-use a variable name and overwrite the value of the original variable.

Variables declared with either let or const have block scope, which means that they are not visible outside of the code block in which they are declared (a code block is essentially a block of code enclosed within braces {...}). This has the advantage that the same variable name can be used for variables in different code blocks, since changing the value of a variable in one code block will not change the value of a variable in another code block that happens to have the same name.

Variables declared with either let or const cannot be redefined within the block in which they are declared. The only real difference between them is that variables defined with let can be updated, whereas variables declared with const cannot. This means that variables declared with const must be initialised at the same time they are declared, and we cannot do something like this:

const person = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: [1955,2,25]
}; // this is an object

person = {
  firstName: "Molly",
  lastName: "Malone",
  dateOfBirth: [1961,10,15]
};
// Uncaught TypeError: invalid assignment to const 'person'

We can however update the properties of an object declared with const. We can also add properties to the object, and delete properties from it (we’ll see how this can be achieved later in this article). Most practitioners use the const keyword when creating objects using the object literal syntax because it allows the object’s properties to be modified but does not allow the object variable to be reassigned to another object.

The Object() constructor

Another way of creating a JavaScript object is to use the Object() constructor. When it comes to the syntax to use, there are two options:

const myObject = new Object();
const myObject = Object();

The only difference here is the use of the new operator. This only really makes a difference if a value is passed as input to Object(). If Object() is called without a value, as in the above example, then it will simply return an empty object, regardless of whether the new keyword is used or not (this will also be the case if we pass a value of either null or undefined to Object()):

const a = Object();
const b = new Object();

console.log(a); // Object { }
console.log(b); // Object { }

Let’s see what happens when we pass a numerical value to the Object() constructor:

const pi = 3.14159265358979;
const euler = 2.718281828459045;

const a = Object(pi);
const b = new Object(euler);

console.log(a); // Number { 3.14159265358979 }
console.log(b); // Number { 2.718281828459045 }

As you can see, whether we use the new keyword or not in this instance makes no difference. The value returned is, in both cases, an object of type Number, because Object() creates an object wrapper for the value passed to it, and returns an object of the type corresponding to that value. If we pass a string literal to Object(), it will return an object of type String, as will be demonstrated by the following example:

const am = "Good morrning!";
const pm = "Good afternoon!";

const a = Object(am);
const b = new Object(pm);

console.log(a); // String { "Good morning!" }
console.log(b); // String { "Good afternoon!" }

What happens if we pass another object as a value to Object()? The return value in this case will simply be the object itself. We can demonstrate this using the person object we created earlier:

const person = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: [1955,2,25]
};

const a = Object(person);
const b = new Object(person);

b.firstName = "Molly";

console.log(person.firstName); // Molly
console.log(a.firstName); // Molly
console.log(b.firstName); // Molly

Our code in this example has created three variables - person, a and b - that all point to the same object in memory. We can assign a different value to the firstName property (or any other property) of that object using any of these object references. Note also that the use of the new keyword makes no difference whatsoever to the outcome. So, can we happily omit the new keyword when using the Object() constructor? In most cases, yes. An exception arises when the value passed to Object() is a constructor function.

We can of course use the Object() constructor to create the person object by creating an empty JavaScript object and subsequently populating it with properties, as shown below. There seems to be very little point in doing this rather than using the object literal syntax unless we don’t know at the time what all of the object’s properties will be, and simply want to create a placeholder object.

const person = Object();
.
.
.
person.firstName = "John";
person.lastName = "Doe";
person.dateOfBirth = [1955,2,25];

console.log(person);
// Object { firstName: "John", lastName: "Doe", dateOfBirth: (3) […] }

Using a constructor function

If the object we want to create is going to be a one-of-a-kind object, then using the either object literal syntax or the Object() constructor will usually suffice. Suppose, however, that we want to create multiple objects of the same kind. Our person object, for example, is perfectly adequate for storing information about a single person, but suppose we want to store information about a large number of people? We may not know exactly how many records we will need to create. It may even be the case, as in many real-world situations, that the number of records required will vary over time.

Using the object literal syntax or the Object() constructor to create a large number of objects of the same kind is not particularly practical or efficient. In order to accommodate current and future requirements for creating objects of type person, we will write a constructor function. In JavaScript, a constructor function is a special kind of function that is used to create and instantiate objects, and can be used to define an object’s properties and methods. Constructor functions are particularly useful if you need to create a large number of objects of the same kind. Consider the following code:

function Person (firstName, lastName, dateOfBirth) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.dateOfBirth = dateOfBirth;
}

const jdoe = new Person( "John", "Doe", [1955,2,25]);

console.log(jdoe.firstName, jdoe.lastName, jdoe.dateOfBirth);
// John Doe Array(3) [ 1955, 2, 25 ]

The constructor function is created in much the same way as any other function, using the function keyword followed by the function name and a parenthesised, comma-separated list of arguments. The widely adopted convention is for the constructor function name to start with a capital letter. The body of the function follows, enclosed between a pair of braces. This is where the object’s properties and methods are defined. Note that when calling a constructor function to create an object, it is necessary to use the new keyword. If new is omitted, an error will occur.

When the Person() constructor function is called, it binds the values passed to it as arguments to the properties of the object, namely firstName, lastName and dateOfBirth. Note the use of the this keyword, which in JavaScript is used as a reference to the current object (in this case, the object jdoe). Once the code inside the body of the constructor function has been executed, the function returns this (the newly created object).

What happens if we call the Person constructor without passing any arguments to it? A new object will still be created, but each of the object’s properties will be assigned a value of undefined, as we can demonstrate:

function Person (firstName, lastName, dateOfBirth) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.dateOfBirth = dateOfBirth;
}

const jdoe = new Person();

console.log(jdoe.firstName, jdoe.lastName, jdoe.dateOfBirth);
// undefined undefined undefined

A constructor function provides a template for creating multiple objects of the same type by defining the properties and methods that will be common to all objects of that type. When you create a constructor function in JavaScript, a prototype object is created that embodies the properties and methods defined by the constructor function. Any object subsequently created by calling the constructor function inherits those properties and methods.

An object created using a constructor function is unique. If you add a property or method to the object, it does not affect any other objects that have been created using the constructor function. Nor does it affect the constructor function’s prototype object. Changes to the constructor function’s prototype object, on the other hand, will have consequences, not only for any objects subsequently created with the constructor function, but for objects that have already been created using it, as demonstrated by the following code:

function Person (firstName, lastName, dateOfBirth) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.dateOfBirth = dateOfBirth;
}

const jdoe = new Person("John", "Doe", [1955, 2, 25]);

console.log(jdoe.gender); // undefined

Person.prototype.gender = "male";

const jsmith = new Person("John", "Smith", [1958, 8, 14]);
const jjenkins = new Person("Jane", "Jenkins", [1974, 11, 19]);

jjenkins.gender = "female";

console.log(jdoe.gender); // male
console.log(jsmith.gender); // male
console.log(jjenkins.gender); // female

From the above, it can be seen that if we change the definition of the properties and methods in the constructor function’s object prototype, those changes will proliferate to all objects created with that constructor function. This can be incredibly useful if, after creating a large number of objects of a given type, we are asked to add a new property or method. It avoids the necessity to add the additional properties and methods to each of the already-created objects individually, which would be both time consuming and tedious (although non-default property values would still need to be assigned individually).

Object.create()

Another way of creating an object in JavaScript is to use the Object.create() method. It creates an object of the type passed to it, which must be either an object or null. For example, the following lines of code both create an empty object:

const myObject1 = Object.create({});
const myObject2 = Object.create(null);

console.log(myObject1); // Object { }
console.log(myObject2); // Object { }

The Object.create() method allows you to create a new object using an existing object as its prototype. Any object created in this way inherits all of the properties and methods of the prototype object. Using Object.create(), you can create multiple objects of the same type without having to write a constructor function.

For example, suppose we have created an object called person that holds information about a person. We might later wish to create one or more additional objects of the same type. We could use the person object as a prototype to create an additional object with the same properties and methods using Object.create():

const person = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: [1955,2,25]
};

.
.
.

const person2 = Object.create(person);

person2.firstName = "Molly";
person2.lastName = "Malone";
person2.dateOfBirth = [1958,8,14];

console.log(person.firstName); // John
console.log(person2.firstName); // Molly

If at some point you need to create a relatively small number of additional objects - one or two, say - with the same properties and methods as an existing object, this might be the way to go. If there is a requirement for a large number of objects of the same type, we would suggest using a constructor function which, if correctly written, allows you to create and initialise an object based on the constructor function’s prototype in one line of code.

Before we move on, we should mention that Object.create() has a second, optional parameter called propertiesObject. This parameter allows us to exercise a good deal of control over the object creation process, including assigning new values to existing properties and adding new properties and methods. Consider the following code:

const person = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: [1955,2,25]
};

const person2 = Object.create(person, {
  firstName: {
    value: "Casey"
  },
  lastName: {
  value: "Jones"
  },
  dateOfBirth: {
    value: [1958,8,14]
  },
  occupation: {
    writable: true,
    configurable: true,
    value: "Engineer"
  }
});

console.log(person.firstName, person.lastName, person.occupation);
// John Doe undefined
console.log(person2.firstName, person2.lastName, person2.occupation);
// Casey Jones Engineer

The above code creates an object called person using the object literal syntax, and then uses Object.create() to create a second object called person2 using person as the prototype object. The propertiesObject parameter changes the values inherited from the prototype object for the firstName, lastName and dateOfBirth properties. It also adds one additional property to the new object – occupation – and sets its value to “Engineer”. The person2 object is thus an enhanced version of the person object.

Two additional data descriptor keys have been set for the occupation property in addition to value. The writable key is set to true if a property’s value may be changed, and false if it may not. The configurable key is also set to true or false depending on whether the type of the property descriptor may be changed, or the property deleted from the object. We could of course use Object.create() to create an object from the ground up, like this:

const person = Object.create({},
{
  firstName: {
    writable: true,
    configurable: true,
    value: "John"
  },
  lastName: {
    writable: true,
    configurable: true,
    value: "Doe"
  },
  dateOfBirth: {
    writable: true,
    configurable: true,
    value: [1955,2,25]
  }
});

console.log(person.firstName, person.lastName, person.dateOfBirth);
// John Doe Array(3) [ 1955, 2, 25 ]

It would obviously be far quicker and easier to create the person object using the object literal syntax, so it is difficult to envisage a situation in which we would create an object from scratch using Object.create() unless we had something very specific in mind in terms of setting up the data descriptor keys.

Accessing properties

As we have seen, an object’s properties hold data about the object, and can be of any type, including another object. The person object we created earlier, for example, has the properties firstName, lastName and dateOfBirth. The first two properties contain string values representing the person’s first name and last name, while the last property contains an array that holds numerical values corresponding to the year, month and day of the person’s birth.

Most of the time we will probably be creating objects using either the object literal syntax or a constructor function, depending on whether we are creating a one-of-a-kind object or multiple objects of the same type. Either way, the likelihood is that we will be creating the object’s properties and assigning values to them as part of the object creation process. Once we have created the object, we will want to make use of it in our script or program.

We have already seen numerous examples of how to access the values stored in an object’s properties. A property’s value is accessed using the object name and the name of the property, separated by a period (this is commonly referred to as dot notation). Suppose we want to retrieve the full name of the person represented by a person-type object similar to the one we created earlier and place it in a separate variable. Consider the following code:

function Person (firstName, lastName, dateOfBirth) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.dateOfBirth = dateOfBirth;
}

const jdoe = new Person("John", "Doe", [1955, 2, 25]);

let jdoeFullName = jdoe.firstName + " " + jdoe.lastName;

console.log(jdoeFullName); John Doe

The code creates a person() constructor function and uses it to create an object of type person called jdoe. We then create a variable called jdoeFullName and assign to it the concatenated values of the firstName and lastName properties of the jdoe object, separated by a space. The code is relatively straightforward, but it illustrates how dot notation is used to access object property values.

Property values can also be accessed using bracket notation. One reason for using this notation is that it enables you to access a property whose name consists of more than one word. For example:

function Person (fname, lname, dob) {
  this["first name"] = fname;
  this["last name"] = lname;
  this["date of birth"] = dob;
}

const jdoe = new Person("John", "Doe", [1955, 2, 25]);

let jdoeFullName = jdoe["first name"] + " " + jdoe["last name"];

console.log(jdoeFullName); // John Doe
console.log(jdoe["date of birth"]); // Array(3) [ 1955, 2, 25 ]

The bracket notation is also required if you use a numerical value to name a property. For example:

const myObject = {
  3.14159265358979: "Pi"
};

console.log(myObject["3.14159265358979"]); // Pi

We include these examples for the sake of completeness - and in case you ever have to maintain code written by someone else that uses bracket notation. You can use bracket notation with any property name, but why would you? dot notation is far less cumbersome and easier to use. Our recommendation is that you don’t use property names consisting of multiple words or numerical values unless you have a very good reason to do so, and forget about bracket notation for the time being.

The hasOwnProperty() method

It may occur from time to time that, when you try to access the value of a property, a value of undefined is returned. This can occur for one of two reasons. The first reason is that the value assigned to the property could actually be undefined. The second reason is that the property simply doesn’t exist. The following code demonstrates this:

const myObject = {
   property1: undefined,
   property2: null
};

console.log(myObject.property1); // undefined
console.log(myObject.property2); // null
console.log(myObject.property3); // undefined

There are a number of reasons why a specific property does not exist in a particular object. It may have been inadvertently deleted from the object, or even purposely removed at some time, for some reason. More probably, it never existed in the first place. This can happen when you have a large number of objects, all of the same nominal type, that have been created at different times, perhaps using different methods.

Even if a group of related objects has been created using a constructor function, properties could have been added to or removed from individual objects in the group at various times for one reason or another. It could also be the case that one or more properties have been added to all of the objects in the group in order to hold an additional data because the program’s requirements have changed, but the additions have been made at different times, perhaps using different property names.

There is a potentially serious problem here because assigning a value to a non-existent property on an object creates that property on the object. Furthermore, assigning the value of a non-existent property to a variable simply assigns the value undefined to that variable. Our script or program will continue to function normally, and no errors will be reported. There are several ways in which we can check whether a property exists for a given object. We will look first at the hasOwnProperty() method. Consider the following code:

const person = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: [1955,2,25]
};

console.log(person.hasOwnProperty("firstName")); // true
console.log(person.hasOwnProperty("age")); // false

Because we created the person object using the object literal syntax, its prototype is the JavaScript Object.prototype object, which means that it inherits the hasOwnProperty() method from its prototype. This might not work as expected if the hasOwnProperty() method has been shadowed (overridden), since any changes to the Object.prototype object are seen by all objects in the prototype chain. There is also the possibility that methods inherited from Object.prototype have been shadowed further along the prototype chain.

The hasOwn() method

There is another potential problem. We showed earlier that an object can be created using the Object.create() method. The method creates an object of the type passed to it, which must be either an object or null. Consider the following code:

const person = Object.create({});

person.firstName = "John";

console.log(person.hasOwnProperty("firstName")); // true

The first line of code here creates an empty object whose prototype is Object.prototype. Let’s see what happens if we replace the empty pair of braces ({}) with null:

const person = Object.create(null);

person.firstName = "John";
console.log(person.hasOwnProperty("firstName"));
// Uncaught TypeError: person.hasOwnProperty is not a function

In this second example we have explicitly used null as the object prototype for the Object.create() method, which means that not only is the object empty, it also does not inherit the properties and methods defined for Object.prototype. This includes the Object.prototype.hasOwnProperty() method. Any objects that have null as their prototype, or that have an object with null as its prototype in their prototype chain, will not have access to the properties and methods of Object.prototype.

There is a workaround, which is to call the hasOwnProperty() method from Object.prototype. For example:

const person = Object.create(null);

person.firstName = "John";

console.log(Object.prototype.hasOwnProperty.call(person, "firstName"));
// true

This works, but as you can see it is somewhat unwieldy. The recommended alternative to the hasOwnProperty() method is the newer hasOwn() method, which is intended to replace it - a static method that can be used on any object regardless of the nature of that object’s prototype. The following code demonstrates how the hasOwn() method is used:

const person = Object.create(null);

person.firstName = "John";

console.log(Object.hasOwn(person, "firstName")); // true
console.log(Object.hasOwn(person, "age")); // false

The only caveat with regard to the use of hasOwn() is that, because it was introduced to the JavaScript language relatively recently (ECMAScript 2022), some older web browsers do not support it, although it is supported by current versions of all popular browsers as far as we can ascertain at the time of writing.

The "in" operator

When we create an object using the object literal syntax or a constructor function, the object will have the Object.prototype object as its prototype. Using the Object.create() method, we can use either another object or null as the prototype. The only thing we can be certain of is that the object we create will have the properties and methods we have explicitly defined for it, together with any properties and methods it inherits from its prototype (and as we have previously mentioned, if the prototype is null, no properties or methods are inherited).

Simply put, unless we have used null as a prototype, we cannot always be sure what properties and methods are available to an object because we cannot always be sure what has occurred further down the prototype chain. If our object has the Object.prototype object as its prototype, it will inherit the properties and methods of the Object.prototype object (although we should keep in mind that they may have been overriden).

If our object has another object as its prototype, we can’t be sure that it will inherit properties and methods from the Object.prototype object, because the object we have selected as a prototype may be a null prototype object, or be descended from another null prototype object. The methods we have looked at so far - hasOwnProperty() and hasOwn() - do not help us to determine whether or not a particular property has been inherited, as illustrated by this code:

const person = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: [1955,2,25]
};

const person2 = Object.create(person);
console.log(person2.firstName); // John
console.log(Object.hasOwn(person2, "firstName")); // false

Clearly, the person2 object has inherited the firstName property from its prototype, the person object, but when hasOwn() is called on the firstName property for person2 it returns false. We can determine whether a property is in an object’s prototype chain, however, using the in operator:

const person = {
  firstName: "John",
  lastName: "Doe",v   dateOfBirth: [1955,2,25]
};

const person2 = Object.create(person);

console.log("firstName" in person2); // true

When the in operator is applied to a property, it returns true if that property exists anywhere in the prototype chain, otherwise it returns false. You may be asking at this point why we don’t simply check whether a property exists in an object or its prototype chain by accessing its value. This code demonstrates the principle:

const myObject = {
  property1: 100,
};

const myObject2 = Object.create(myObject);

console.log(myObject2.property1); // 100
console.log(myObject2.property2); // undefined

As you can see, because property2 does not exist in the prototype chain, its value is reported as undefined. However, consider this code:

const myObject = {
  property1: 100,
  property2: undefined
};

const myObject2 = Object.create(myObject);

console.log(myObject2.property1); // 100
console.log(myObject2.property2); // undefined

The value of property2 is still reported as undefined because, even though the property does exist in the prototype chain, we have explicitly assigned a value of undefined to it. Clearly, then, if a property has a value of undefined, we should not simply assume that it doesn’t exist in the prototype chain. When applied to a property, the in operator will always return true if that property exists somewhere in the prototype chain, even when the value assigned to that property is undefined.

Enumerating properties

According to Wikipedia: “An enumeration is a complete, ordered listing of all the items in a collection.” It may well be the case that we wish to obtain an ordered listing of all the properties belonging to an object, and this can be achieved in three different ways.

We’ll look first at the Object.keys() method, which returns an array containing the names of all of the enumerable properties belonging to the object itself, but excludes any inherited properties (a property’s enumerable attribute may be set to true or false depending on whether or not a property may be listed in an enumeration - by default it is set to true). The following code demonstrates how this method is used:

const person = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: [1995,2,25],
}

const person2 = Object.create(person);

person2.occupation = "Engineer";

const keys = Object.keys(person);
const keys2 = Object.keys(person2);

console.log(keys); // Array(3) [ "firstName", "lastName", "dateOfBirth" ]
console.log(keys2); // Array [ "occupation" ]

The next method we’ll look at is the Object.getOwnPropertyNames() method. This method essentially does the same thing as the Object.keys() method except that it returns an array containing the names of all of the properties belonging to the object itself, regardless of whether or not their enumerable attribute is set to true or false. The following code demonstrates the difference between the two methods:

const person = {
  firstName: "John",
  lastName: "Doe",
}

const person2 = Object.create(person);

person2.occupation = "Engineer";

Object.defineProperty(person2, "employeeNumber", {
  value: 12345,
  enumerable: false,
});

const names = Object.getOwnPropertyNames(person2);
const keys = Object.keys(person2);

console.log(names); // Array [ "occupation", "employeeNumber" ]
console.log(keys); // Array [ "occupation" ]

The final enumeration technique we will look at employs a for...in loop to iterate over an object’s enumerable properties. The general syntax is as follows:

for(let key in object) {
  // ...
};

The difference between using this technique and either of the two enumeration methods we have already looked at is that it will traverse all of the enumerable properties of the object and its prototype chain (properties inherited from Object.prototype are not enumerable). The following code demonstrates how this works:

const person = {
  firstName: "John",
  lastName: "Doe",
}

const person2 = Object.create(person);

person2.occupation = "Engineer";
person2.employeeNumber = 12345;

const person3 = Object.create(person2);

person3.hairColour = "Brown";
person3.eyeColour = "Blue";

for(let key in person3) {
console.log(person3[key]);
};
// Brown
// Blue
// Engineer
// 12345
// John
// Doe

Changing a property’s value

There are many reasons why we might want to change the value of one or more properties belonging to an object. For example, we might have a person object that holds information about a specific individual, such as name, address, date of birth, and so on. Some of these details - the person’s address, for example, may change over time and need to be updated. Similarly, if we create a new object using another object as a prototype, we will probably want to change many, if not all, of the inherited property values.

Changing the value of an existing property is straightforward, and is achieved using the assignment operator. Consider the following example:

const person = {
  firstName: "John",
  lastName: "Doe"
};

const fsmith = Object.create(person);

fsmith.firstName = "Fred";
fsmith.lastName = "Smith";

console.log(fsmith);
// Object { firstName: "Fred", lastName: "Smith" }

When creating a new object with the Object.create() method, we can also change the inherited property values using the method’s optional propertiesObject parameter, as demonstrated by the following code:

const person = {
  firstName: "John",
  lastName: "Doe"
};

const fsmith = Object.create(person, {
firstName: { value: "Fred" },
lastName: { value: "Smith" }
});

console.log(fsmith.firstName, fsmith.lastName); // Fred Smith

Adding a property

We have already seen numerous examples of properties being added to an object during the object creation process. Unlike some other languages (C# and Java, for example) it is easy to add a property to an object after it has been created. This is very convenient if we create an object to represent a real-world object for which the set of data items that describe it can expand over time due to changing application requirements. We have seen, for example, how we can create an object to represent a person:

const person = {
  firstName: "John",
  lastName: "Doe"
};

Suppose we subsequently find we need to keep a record of that person’s telephone number and email address. We can easily add these properties:

person.tel = "01234 567890";
person.email = "jdoe@hotmail.com"

console.log(person);
// Object { firstName: "John", lastName: "Doe", tel: "01234 567890",
  email: "jdoe@hotmail.com" }

Suppose we create an object using another object as a prototype with the Object.create() method. What happens if we later add an additional property to the prototype object? Consider the following code:

const person = {
  firstName: "John",
  lastName: "Doe"
}

const person2 = Object.create(person);

console.log(person2.planetOfOrigin); // undefined

// Add the "planetOfOrigin" property to person
// and assign a default value of "Earth"
person.planetOfOrigin = "Earth";

console.log(person2.planetOfOrigin); // Earth

As you can see, a property that is added to an object’s prototype (or any object in the prototype chain) after the object has been created also becomes a property of the object. This is very convenient feature, because it means that if we have created a large number of objects using another object as a prototype, and later decide that all of those objects need the same additional property, we only have to add the property to the prototype in order for that property to be added to all of its descendants.

Deleting a property

Deleting properties is also relatively straightforward, and is achieved using the delete operator. Consider the following code:

function Person (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const jdoe = new Person( "John", "Doe");

console.log(jdoe.firstName); // John

delete jdoe.firstName;
delete jdoe.age;

console.log(jdoe.firstName); // undefined
console.log("firstName" in jdoe); // false

As you can see from the code, deleting a property that doesn’t exist - in this case the age property - does not produce an error. JavaScript simply ignores it. JavaScript will also ignore any attempt to delete an inherited property from an object. For example:

const person = {
  firstName: "John",
  lastName: "Doe"
}

const person2 = Object.create(person);

delete person2.firstName;

console.log(person2.firstName); // John

Deleting an inherited property from an object’s prototype - or from whatever object in the object’s prototype chain the property was defined on - will delete the property from the object itself, as demonstrated by the following code:

const person = {
  firstName: "John",
  lastName: "Doe",
  planetOfOrigin: "Earth"
}

const person2 = Object.create(person);

console.log(person2.planetOfOrigin); // Earth

// Delete the "planetOfOrigin" property from person
delete person.planetOfOrigin;

console.log(person2.planetOfOrigin); // undefined

Creating and accessing methods

A method is a function that is defined on an object. Putting this another way, a method is a property of an object that is also a function. A method can be defined in the same way as any other function, but it is invoked as a property of the object on which it is defined, using dot notation. So far, we have created a number of objects with properties, but have not defined any methods for those objects. We can add a function to an object when we create the object. The syntax for defining a function is as follows:

functionName: function(parameters) {
  // . . . do something
}

Consider the following example:

const person = {
  firstName: "John",
  lastName: "Doe",
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
}

Let personFullName = person.fullName();

console.log(personFullName); // John Doe

We have created an object called person that has two properties, firstName and lastName. The fullName() function does not take any parameters. It simply concatenates the values assigned to firstName and lastName, with a space in between them, and returns the resulting string.

We can also define a method using a constructor function:

function Person (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.fullName = function() {
    return firstName + " " + lastName;
  };
}

const person1 = new Person("John", "Doe");

let personFullName = person1.fullName();

console.log(personFullName); // John Doe

The function declaration for the fullName() method is the same as the one we wrote for the person object we created using the object literal syntax, except that this time we must precede it with the this keyword, with a dot separating this and the function name. We call the fullName() method in exactly the same way we did before, using dot notation. It is important to remember to include the parentheses when calling a method, even if the method does not have any formal parameters. If you omit the parentheses, JavaScript returns a function reference instead of the function’s return value. For example:

function Person (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.fullName = function() {
  return firstName + " " + lastName;
  };
}

const person1 = new Person("John", "Doe");

let personFullName = person1.fullName;

console.log(personFullName); // function fullName()

As with other object properties, we can declare a method for an object after the object has been created. For example:

const person = {
  firstName: "John",
  lastName: "Doe"
}

person.fullName = function() {
  return this.firstName + " " + this.lastName;
}

console.log(person.fullName()); // John Doe

If we call a non-existent method on an object, an error occurs:

const myObject = {
  property1: 100,
  property2: 200
}

console.log(myObject.someMethod());
// Uncaught TypeError: person.someMethod is not a function

Suppose we create a new object using another object that has no methods as a prototype. If we subsequently add a method to the prototype object, can we call that method from the new object? As with properties, a method that is added to an object’s prototype (or any object in the prototype chain) after the object has been created also becomes a method of the object. The following code shows how this works:

const object1 = {
  property1: 100,
  property2: 200
}

object2 = Object.create(object1);
object2.property1 = 500;

object1.someMethod = function() {
  return this.property1;
}

console.log(object2.someMethod()); // 500

Besides using a function expression, you can define a function completely separately and add it to the object as a property, as demonstrated by the following code:

const person = {
  firstName: "John",
  lastName: "Doe",
  fullName: generateName
}

function generateName() {
  return this.firstName + " " + this.lastName;
}

console.log(person.fullName()); // John Doe

Getter and setter methods

We want to look now at so-called getter and setter methods. We can think of them as a special kind of object property. The object properties we have looked at so far have been data properties. Each data property has a name, and a value that is associated with that name. There is another kind of object property called an accessor property.

One important thing to note is that a given property name can refer to either a data property or an accessor property, but not both. Accessor properties look like data properties to external code, but behind the scenes they are implemented as functions that execute automatically when we get or set their values. To demonstrate how we might write a getter method, we’ll revisit our trusty person object once more:

const person = {
  firstName: "John",
  lastName: "Doe",
  get fullName() {
     return this.firstName + ' ' + this.lastName;
  }
}

console.log(person.fullName); // John Doe

Essentially, the only significant difference between the fullName() getter method we have used here and the fullName() function we have used previously is that the function keyword has been replaced by the get keyword. Note also that when accessing the fullName accessor property, we do not need to add parentheses after the property name - the getter method will be executed automatically.

As things stand, we can’t assign a value to the fullName accessor property because it is not a regular data property, and there is as yet no setter method for it. The following code demonstrates how we can remedy this situation:

const person = {
  firstName: "John",
  lastName: "Doe",
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  },
  set fullName(value) {
    [this.firstName, this.lastName] = value.split(" ");
  }
}

person.fullName = "Molly Malone";

console.log(person.fullName); // Molly Malone

You could of course achieve the same end result using ordinary functions instead of getter and setter methods. One advantage of using getter and setter methods is they enable us to implement accessor properties - we can perhaps think of them as virtual properties - whose values can be read and written in the same way we read and write the values of data properties.

Another advantage of getter and setter methods is that they enable us to define accessor properties whose value is dependent on the value of one or more data properties. We can perhaps think of them as computational values. The value of the fullName accessor property in our person object, for example, is computed by concatenating the values of two data properties - firstName and lastName - and adding a space between them.

Another example of a property that should ideally be implemented as an accessor property rather than a data property is a person’s age, since its value will change on a yearly basis. Imagine working in the human resources department of a large company and having to update your personnel records every time someone has a birthday!

It is of course useful to be able to access a person’s record and see their current age at a glance. This can be achieved, however, by storing their date of birth as a data property. The person’s age can then be implemented as an accessor property whose value is computed using their date of birth and the current date. The following code demonstrates how this might be implemented:

const person = {
  firstName: "John",
  lastName: "Doe",
  dateOfBirth: "1955-02-25",
  get age() {
    let today = new Date();
    let dob = new Date(this.dateOfBirth);
    let age = today.getFullYear() - dob.getFullYear();
    let m = today.getMonth() - dob.getMonth();
    if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) {
      age--;
    }
    return age;
  }
}

console.log(person.age); // 68

Inheritance and the prototype chain

As we have seen, when a user defined object is created there are three possibilities with regard to its prototype. If we define an object using the object literal syntax, or with the Object() constructor, or by using a constructor function, then the object’s prototype will be the Object.prototype object, in which case the newly created object will inherit the properties and methods of Object.prototype.

Two additional possibilities arise when we create a new object using the Object.create() method. The argument passed to this method can only be another object or null. If the argument is null, then the newly created object will not inherit any properties or methods from anywhere; we basically have an empty object with no properties or methods - a blank slate if you like - and there are situations in which this can be a highly desirable state of affairs.

If we pass an existing object to Object.create() as its argument, then the newly created object will inherit any properties and methods defined on the prototype object, or that have been inherited by the prototype object. We could even, in fact, pass the Object.prototype object itself to Object.create() as an argument, although this would seem to defeat the object of using the Object.create() method in the first place.

The important thing to remember is that objects inherit properties and methods from their prototype. If the prototype is null, no properties or methods are inherited. If the prototype is another object, the properties defined for that object will be inherited, along with any properties and methods inherited by the prototype object. In other words, Objects have prototypes, which can themselves have prototypes, which can also have prototypes, and so on . . . well, hopefully you get the picture.

The prototype chain always ends with either null or the Object.prototype object. Actually, the chain always ends with null, because the prototype of the Object.prototype object itself is null. In fact, the Object.prototype is the only JavaScript object that cannot have its prototype re-assigned, and for that reason it is described as immutable. And while we are on the subject of re-assigning an object’s prototype - don’t do it! Whilst it is certainly possible, it has a negative impact on the performance of the JavaScript engine in virtually all browsers, not to mention altering inheritance in ways that can lead to unpredictable behaviours.

Almost all objects in JavaScript are descended from the Object.prototype object, and as such they inherit that object’s properties and methods. As we have seen, the only objects that do not inherit properties from Object.prototype are objects whose prototype is null, or that have an object with null as its prototype in their prototype chain. Any user-defined object that does not have a null prototype in its prototype chain thus inherits the following properties from Object.prototype:

toString
toLocaleString
valueOf
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
constructor

We will be providing a more detailed summary of the Object.prototype properties at the end of this article. The important thing to realise here is that all of these properties can be shadowed (overridden), either on the Object.prototype object itself or, as is more often the case, elsewhere in the prototype chain. Any changes to the Object.prototype object itself will be seen by all objects descended from it, unless those changes have themselves been overridden further along the prototype chain.

On the face of it, the ability to override the Object.prototype properties seems to present a golden opportunity to extend object behaviour. However, it is also potentially very dangerous, and can lead to unexpected results. For that reason, it is recommended to avoid the use of Object.prototype methods. The only exceptions to this guiding principle are toString(), toLocaleString(), and valueOf(). These methods were intended to be polymorphic, which essentially means that their default behaviour can be modified in order to meet specific requirements, or to produce results in a form that depends on the number and type of arguments they receive.

The __defineGetter__, __defineSetter__, __lookupGetter__, __lookupSetter__ and __proto__ properties have all now been deprecated and should not be used. We will be summarise the static methods that have replaced these and other Object.prototype methods later in this article.

On way to prevent potential problems arising because one or more Object.prototype properties have been overridden is to explicitly create new objects with null as their prototype. For example:

const myObject1 = Object.create({});
const myObject2 = Object.create(null);

myObject1.someProperty = "1234";
myObject2.someProperty = "5678";

console.log(myObject1.valueOf()); // Object { someProperty: "1234" }

console.log(myObject2.valueOf());
// Uncaught TypeError: myObject2.valueOf is not a function

As you can see, because the myObject2 object does not inherit any properties from Object.prototype, calling the valueOf() method on it creates an error. As we have already mentioned, the generally accepted view is that the use of Object.prototype methods should be avoided. We also mentioned, however, that the toString(), toLocaleString(), and valueOf() methods are the exceptions to that rule because they were specifically designed to be overridden and, unlike other Object.prototype methods, they have not been deprecated or declared obsolete.

Fortunately, we can still use these methods. For example, we can call an Object.prototype method on the Object.prototype object directly, making our null-prototype object the target of the call. The following code demonstrates the principle:

const myObject = Object.create(null);

myObject.someProperty = "1234";

console.log(Object.prototype.valueOf.call(myObject));
// Object { someProperty: "1234" }

We could also simply add the original methods back to the null-prototype object, like this:

const myObject = Object.create(null);

myObject.someProperty = "1234";

myObject.toString = Object.prototype.toString;
myObject.toLocaleString = Object.prototype.toLocaleString;
myObject.valueOf = Object.prototype.valueOf;

console.log(myObject.valueOf());
// Object { someProperty: "1234", toString: toString(),
// toLocaleString: toLocaleString(), valueOf: valueOf() }
console.log(Object.keys(myObject));
// Array(4) [ "someProperty", "toString", "toLocaleString", "valueOf" ]

As you can see, unlike objects that do not have null as their prototype, myObject now has the toString(), toLocaleString() and valueOf() methods as its own properties rather than as inherited properties. Essentially, we now have the best of both worlds because we can still access these methods but have not inherited any other potentially dangerous properties (because they may have been overridden) from Object.prototype.

Built-in properties and methods

JavaScript’s built-in properties and methods fall into two categories. The first category includes the properties and methods defined on the Object.prototype object itself. The table below lists these properties and methods, which are often referred to as instance properties and methods because they are inherited by every object that is either an instance of Object.prototype or that has Object.prototype in its prototype chain. The descriptions are primarily derived from MDN’s JavaScript Reference.



Object.protoype properties and methods
Instance properties
PropertyDescription
Object.prototype.constructor

The constructor data property of an Object instance returns a reference to the constructor function that created the instance object. Note that the value of this property is a reference to the function itself, not a string containing the function's name.

Object.prototype.__proto__

The __proto__ accessor property of Object instances exposes the [[Prototype]] (either an object or null) of this object.

The __proto__ property can also be used in an object literal definition to set the object [[Prototype]] on creation, as an alternative to Object.create().

Deprecated: This feature is no longer recommended. Avoid using it, and update existing code if possible. Be aware that this feature may cease to work at any time.

Instance methods
MethodDescription
Object.prototype.__defineGetter__()

Binds an object's property to a function to be called when that property is looked up.

Deprecated: This feature is no longer recommended. Avoid using it, and update existing code if possible. Be aware that this feature may cease to work at any time.

Object.prototype.__defineSetter__()

Binds an object's property to a function to be called when an attempt is made to set that property.

Deprecated: This feature is no longer recommended. Avoid using it, and update existing code if possible. Be aware that this feature may cease to work at any time.

Object.prototype.__lookupGetter__()

Returns the function bound as a getter to the specified property.

Deprecated: This feature is no longer recommended. Avoid using it, and update existing code if possible. Be aware that this feature may cease to work at any time.

Object.prototype.__lookupSetter__()

Returns the function bound as a setter to the specified property.

Deprecated: This feature is no longer recommended. Avoid using it, and update existing code if possible. Be aware that this feature may cease to work at any time.

Object.prototype.hasOwnProperty()

Returns a boolean indicating whether the object has the specified property as its own property (as opposed to inheriting it).

Note: Object.hasOwn() is recommended over hasOwnProperty(), in browsers where it is supported.

Object.prototype.isPrototypeOf()

Checks if an object exists in another object's prototype chain.

Object.prototype.propertyIsEnumerable()

Returns a boolean indicating whether the specified property is the object's enumerable own property.

Object.prototype.toLocaleString()

Returns a string representing the object. This method is meant to be overridden by derived objects for locale-specific purposes.

Object.prototype.toString()

Returns a string representing the object. This method is meant to be overridden by derived objects for custom type conversion logic.

Object.prototype.valueOf()

The valueOf() method of Object instances converts the this value to an object. This method is meant to be overridden by derived objects for custom type conversion logic.



Almost all objects in JavaScript are instances of Object. As we have seen, they can be created using the Object() constructor or by using the object literal syntax. Object has a number of built-in methods that we can use to work with individual objects. These methods are static methods because they belong to Object itself, unlike instance methods that belong to the Object.prototype object, and are inherited by all instances of Object.prototype. They are called on Object rather then on instances of Object, and take an object instance as a parameter.



Object (static) methods
MethodDescription
Object.assign()

Copies all enumerable own properties from one or more source objects to a target object. It returns the modified target object.

Object.create()

Creates a new object, using an existing object as the prototype of the newly created object.

Object.defineProperties()

Defines new or modifies existing properties directly on an object, returning the object.

Object.defineProperty()

Defines a new property directly on an object, or modifies an existing property on an object, and returns the object.

Object.entries()

Returns an array of a given object's own enumerable string-keyed property key-value pairs.

Object.freeze()

Freezes an object to prevent extensions and make existing properties non-writable and non-configurable. A frozen object can no longer be changed: new properties cannot be added, existing properties cannot be removed, their enumerability, configurability, writability, or value cannot be changed, and the object's prototype cannot be re-assigned. freeze() returns the same object that was passed in.

Freezing an object is the highest integrity level that JavaScript provides.

Object.fromEntries()

Transforms a list of key-value pairs into an object.

Object.getOwnPropertyDescriptor()

Returns an object describing the configuration of a specific property on a given object (that is, one directly present on an object and not in the object's prototype chain). The object returned is mutable but mutating it has no effect on the original property's configuration.

Object.getOwnPropertyDescriptors()

Returns all own property descriptors of a given object.

Object.getOwnPropertyNames()

Returns an array of all properties (including non-enumerable properties except for those which use Symbol) found directly in a given object.

Object.getOwnPropertySymbols()

Returns an array of all symbol properties found directly upon a given object.

Object.getPrototypeOf()

Returns the prototype (i.e. the value of the internal [[Prototype]] property) of the specified object.

Object.hasOwn()

Returns true if the specified object has the indicated property as its own property. If the property is inherited, or does not exist, the method returns false.

Object.is()

Determines whether two values are the same value.

Object.isExtensible()

Determines if an object is extensible (whether it can have new properties added to it).

Object.isFrozen()

Determines if an object is frozen.

Object.isSealed()

Determines if an object is sealed.

Object.keys()

Returns an array of a given object's own enumerable string-keyed property names.

Object.preventExtensions()

Prevents new properties from ever being added to an object (i.e. prevents future extensions to the object). It also prevents the object's prototype from being re-assigned.

Object.seal()

Seals an object, to prevent extensions and make existing properties non-configurable. A sealed object has a fixed set of properties: new properties cannot be added, existing properties cannot be removed, their enumerability and configurability cannot be changed, and its prototype cannot be re-assigned. Values of existing properties can still be changed as long as they are writable. seal() returns the same object that was passed in.

Object.setPrototypeOf()

Sets the prototype (i.e., the internal [[Prototype]] property) of a specified object to another object or null.

Object.values()

Returns an array of a given object's own enumerable string-keyed property values.