6 Ways to Create Objects in Javascript — Intro to Object & Prototype

Javascript allows you to work with primitives and objects. Perhaps, Javascript objects are one of the most famous and unique objects in the developer world due to their prototype nature. Since objects are such an integral part of Javascript, it comes with many ways to create them each with its pros and cons.

What are Objects in Javascript?

An object is one of the data types in Javascript and it is used to store a keyed collection of things but not all objects are instances of Object. Objects normally inherit properties and methods from Object.prototype unless you don’t want it to, which I will talk about later on.

Besides primitives, almost all structural types are objects, even functions. For this article, we will only focus on the Object object, not its derived ones.

Primitives and Objects?

Yes, primitives and objects are all you got for Javascript. Objects include Function, Date, key collections like Map, Set, WeakMap, and WeakSet, indexed collections like Array and Typed Array, and structure data like JSON. It becomes obvious when you try to check types of different things with the “typeof” operator. Below is some output examples

// primitives
typeof undefined // undefined
typeof true // boolean
typeof false // boolean
typeof 12 // number
typeof '' // string
typeof 10n // bigint
typeof Symbol() // symbol
// special primitive
typeof (null) // object
// weird globals
typeof NaN // number
typeof Number.NaN // number
typeof Infinity // number
// object derived
typeof (new Array()) // object
typeof (new String()) // object
typeof (new Number()) // object
typeof (new Map()) // object
typeof (new Set()) // object
typeof (new WeakSet()) // object
typeof (new WeakMap()) // object
typeof (new Object()) // object
typeof (new Date()) // object
typeof (new Boolean()) // object
// special shorthand for functions only
// but functions are still object derived

typeof (new Function()) // function

Some primitives have Object equivalents but they are not recommended to be used. These are String, Boolean, and Number. It is good to mention that there is a difference when you create a string, number, or boolean using their Object constructors vs their literal version. They will always be of type “object” and you need to check their instance to confirm what kind of object it is.

const str1 = 'some string';
const str2 = new String('some string');
typeof str1 // string
str1 instanceof String // false
typeof str2 // object
str2 instanceof String // true

Object initializer

The easiest way to create an object is to use the object's Initializer using curly braces. You can define getters and setters, properties which values can be any other Javascript data type or structure, as well as methods.

Custom methods, getters, and setters can refer to the object itself through the “this” keyword. The “this” keyword only has an effect on the functions which can bind the “this” value. That means that using arrow functions will not work and arrow functions in objects are treated like any other non-function value for a property.

const person = {
firstName: 'John',
lastName: 'Doe',
get name() {
return `${this.firstName} ${this.lastName}`
},
age: 30,
greet() {
console.log(`Hi! My name is ${this.name}`)
}
}

Objects created with curly braces already inherit members from the Object prototype. That means you can call methods like toString, toLocaleString, isPrototypeOf, hasOwnProperty, and more on objects created with the object initializer.

person.hasOwnProperty('name'); // true
person.hasOwnProperty('sample'); // false

Object constructor

The second way to create objects is to use the Object constructor which is the father of all objects. By default, passing null, undefined, or no value to the Object constructor returns an empty object.

Below is the same “person” object from the example before but as you can see, it required a little more work to accomplish the same thing, especially when it comes to creating getter and setters. You need to make usage of the defineProperty static method in Object to create getters. You can actually use it to define any property you want as well.

Another difference is with the “greet” method which we assign an anonymous function using the function keyword so it has access to the object through the “this” keyword.

const person = new Object();person.firstName = 'John';
person.lastName = 'Doe';
Object.defineProperty(person, 'name', {
get() {
return `${this.firstName} ${this.lastName}`
}
})
person.age = 30;
person.greet = function() {
console.log(`Hi! My name is ${this.name}`)
}

The Object constructor can actually create any object you want as it takes any value. It will return an object with the correct constructor and prototype. You can even pass an object created with curly braces but that feels redundant.

In general, this should not be the preferred way to create an object. You should use curly brace or an appropriate Object constructor like Map or Set for to create your object.

new Object(12); // Number
new Object(''); // String
new Object(true); // Boolean
new Object(() => 10); // Function
new Object([]); // Array
new Object(Symbol()); // Symbol
new Object(new Map()); // Map
new Object(new Set()); // Set
new Object({name: 'John Doe'}); // Object
...

Object create method

The Object has a “create” static method that can be super useful and provides a better way to create objects in general. It also has the advantage that you can specify that you don’t want to inherit the Object prototype. The is great when you want to use the object as data without any special prototype association.

const person = Object.create(null);person.firstName = 'John';
person.lastName = 'Doe';
Object.defineProperty(person, 'name', {
get() {
return `${this.firstName} ${this.lastName}`
}
})
person.age = 30;
person.greet = function() {
console.log(`Hi! My name is ${this.name}`)
}

Because I set null as the first argument, “person” does not inherit the Object prototype which includes methods like hasOwnProperty, therefore, it throws an error if I try to call any Object prototype method. However, you can attach the prototype later on if you want to by using the Object “setPrototypeOf” static method.

person.hasOwnProperty('name'); // TypeError: person.hasOwnProperty is not a method// attach prototype back on
Object.setPrototypeOf(person, Object.prototype)
person.hasOwnProperty('name'); // true

The Object “create” method is super powerful and allows for some amazing things like inheritance and control over the object descriptors. Below is how you can create another object using the person object as a prototype and using the second argument to set descriptors for age and name to override the prototype equivalent members.

const personChild = Object.create(person, {
age: {
value: 5
},
name: {
get() {
return `${this.firstName} ${this.lastName} Jr.`
}
}
})
personChild.age // 5
personChild.name // "John Doe Jr."
personChild.greet() // "Hi! My name is John Doe Jr."

The “personChild” will have its own property of “age” and “name” getter and its prototype is the “person” object containing the “firstName”, “lastName” and “greet” method that we can still access through “personChild”. This is how the prototype inheritance works.

// personChild object inherits from person
{
age: 5
name: "John Doe Jr."
__proto__:
age: 30
firstName: "John"
greet: ƒ ()
lastName: "Doe"
name: "John Doe"
}

Factory Function

The factory function is the only method that is not anyhow related to objects at all but as you could see on the sample code above, it can get messy, and making things private or read-only can be challenging.

The factory function is a way to put something in and get the object you want out allowing for encapsulation of all the logic involved with creating the object. It makes object creation easier and it is one of the design patterns you should use to improve your code with.

function createPerson(name, age) {
const person = Object.create(null);
const [firstName, ...lastNameArray] = name.split(/\s+/);
person.firstName = firstName;
person.lastName = lastNameArray.join(' ');
person.name = name;
person.age = age;
person.greet = function() {
console.log(`Hi! My name is ${this.name}`)
}

return person;
}
const person = createPerson('John Doe', 30);

You can also use factory functions to handle your composition and extension as well. You can either pass the object to extend with or resort to a higher-order function to make the modifications.

// HOF
function withDateOfBirth(fn, dob) {
return (name, dob) => {
const age =
(new Date()).getFullYear() -
(new Date(dob)).getFullYear();
const obj = fn(name, age);
obj.dob = dob;

return obj;
}
}
// uses the createPerson factory function
const createPersonWithDOB = withDateOfBirth(createPerson);
const person = createPersonWithDOB('John Doe', '03/02/1990');// Extension
function extendObject(object, extension) {
const objCopy = {...object};
Object.setPrototypeOf(objCopy, extension);
return objCopy;
}
extendObject(person, {
get initials() {
return `${this.firstName[0]} ${this.lastName[0] ?? ''}`
}
})
/*
{
age: 31
dob: "03/02/1990"
firstName: "John"
greet: ƒ ()
lastName: "Doe"
name: "John Doe"
__proto__:
initials: "J D"
get initials: ƒ initials()

__proto__: Object
}
*/

Constructor Function

A constructor function is very similar to a factory function in the sense that you can encapsulate logic and only expose what you want, but what makes it different is that it genuinely creates an object for you through instantiation. It works internally with “this” keyword which anything set on becomes public.

function Person(name, age) {
const [firstName, ...lastNameArray] = name.split(/\s+/);

this.firstName = firstName;
this.lastName = lastNameArray.join(' ');
this.name = name;
this.age = age;
this.greet = function() {
console.log(`Hi! My name is ${this.name}`)
}
}
const person = new Person('John Doe', 30)
/*
{
age: 30
firstName: "John"
greet: ƒ ()
lastName: "Doe"
name: "John Doe"
__proto__:
constructor: ƒ Person(name, age)
__proto__: Object
}
*/

In general, you want to set the properties on the “this” object and use the prototype to set the methods. Because of this prototype handling, it is best to combine this with the module pattern through the usage of IIFE.

const Person = (() => {
function Person(name, age) {
const [firstName, ...lastNameArray] = name.split(/\s+/);
this.firstName = firstName;
this.lastName = lastNameArray.join(' ');
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hi! My name is ${this.name}`)
}

return Person;
})();
const person = new Person('John Doe', 30);
/*
{
age: 30
firstName: "John"
lastName: "Doe"
name: "John Doe"
__proto__:
greet: ƒ ()
constructor: ƒ Person(name, age)
__proto__: Object
}
*/

What is happening in the above code is we are using the immediately invoked function to return the “Person” constructor function. Since you set a prototype outside of the function, the IIFE will help encapsulate that complexity allowing for a much cleaner and organized code. Anything set in the prototype will be inherited by all instances of that constructor function.

Class

Dealing and building objects can get robust and become hard to maintain or make sense of. All you saw above is the reason “class” got introduced. A Javascript class is just a syntactic sugar over everything you read so far.

A class is doing all the constructor function and Object.create work for you so you dont have to worry about the complexity and the decision involved with creating and organizing your object code. This is why “class” in Javascript is not considered to be “real” compared to how they are implemented in languages like Java or C++.

The below code example gives us the same person object as the constructor function in the previous example.

class Person {
constructor(name, age) {
const [firstName, ...lastNameArray] = name.split(/\s+/);
this.firstName = firstName;
this.lastName = lastNameArray.join(' ');
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi! My name is ${this.name}`)
}
}
const person = new Person('John Doe', 30);
/*
{
age: 30
firstName: "John"
lastName: "Doe"
name: "John Doe"
__proto__:
greet: ƒ ()
constructor: ƒ Person(name, age)
__proto__: Object
}
*/

If you inspect “person” you will see that the “greet” method automatically goes to prototype similar to the approach used in the constructor function module. The class has its own syntax for static and private members and is by far the less messy and easier to make sense of.

Because “class” is syntactic sugar on the constructor function, I can extend a class with and constructor function easily. The inverse is also possible.

function Parent(name) {
this.name = name;
this.isParent = true;
}
class Child extends Parent {
constructor(...args) {
super(...args);

this.isParent = false;
}
}
const parent = new Parent('John');
{
isParent: true
name: "John"
__proto__:
constructor: ƒ Parent(name)
__proto__: Object
}
const child = new Child('John Jr.');
{
isParent: false
name: "John Jr."
__proto__: Parent
constructor: class Child
__proto__: Object
}

Conclusion

As you can see, things can get intense when dealing with objects and you should stick to “class” for simplicity. Instead of using “object” as data, consider Map which is better suited as data, can be more performant, supports non-string keys, and comes with some handy methods and properties. If you want to stick to functional programming then factory functions and Map make for a perfect combo.

Working with objects is fun and you should dive deep into Object prototype and the prototypical inheritance way to do Object-Oriented Programming in Javascript.

YouTube Channel: Before Semicolon
Website: beforesemicolon.com

Blog & YouTube Channel for Web, UI & Software Development - beforesemicolon.comyoutube.com/c/BeforeSemicolon

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store