Reintroduction to Objects in JavaScript — Yet Another Primer (YAP!)
Everything is an object in JavaScript.
In Linux, everything is a file. This means every resource on a Linux system can be used like a file. In JavaScript nearly everything can be treated as an object. This causes a lot of confusion for developers with no or very little JavaScript knowledge. To understand it better let us first understand what is an object? This is gonna be a long read, hence here is a much shorter version or tl;dr for faint-hearted.
There are mainly three ways to create objects
Object literal notation
Simply create an object using literal notation {}
.
const myObject = {};
Using Object.create method
Create a new object or extend an object.
const newObject = Object.create({});
const anotherObject = Object.create(newObject);
This method takes an additional optional parameter, an object with property descriptors. The new object inherits all the properties of the object which was used in the first argument.
Using new
operator
This operator creates a new object using a constructor function.
const newObject = new Object();
Then ways to add and access properties of object.
The dot . operator
Very simple way of setting and accessing properties of an object.
const superman = {};
superman.realname = "clark kent";
console.log(superman.realname);
The square bracket way
Another way to set and access the properties of an object.
superman["livesin"] = "metropolis";
console.log(superman["livesin"]);
This way is preferred for accessing properties when you iterate over an array-like object.
The Object.defineProperty
and Object.defineProperties
Object.defineProperties
is a batch way to create properties in the same way Object.defineProperty
creates properties. Both use a property descriptor object for defining new properties.
And delete operator in JavaScript is not managing memory for you, use it wisely
And that’s the gist.
However, the actual story is very long.
In classical Object Oriented languages like Java, an object is an instance of a class. An object will have some properties, some methods attached to it. For example, this JavaScript article is a concrete implementation of class Article. An article will have certain properties and methods associated with it. These properties can be the title of the article, author name, number of paragraphs etc. JavaScript is different here. Unlike classical object oriented programming languages JavaScript is known as object based language. Object based languages do not support some classical object oriented features such as polymorphism and inheritance out of the box. Object based languages are further classified into prototype based and class based. JavaScript is a prototype based language. As said earlier, it doesn’t support inheritance and polymorphism out of the box, though these features can be implemented in JavaScript in some way. Except null and undefined almost everything in JavaScript is an object and is treated like an object. You can use the `typeof` operator to check what kind of object you are dealing with. As pointed out earlier null is not an object but `typeof null` will return you “object”. This is an interesting bug in the language and can not be fixed as a fix might break existing code.
In JavaScript, object can be created in following three ways:
Using object literal notation
Using object literal notation, an empty JavaScript object can be created quickly. One can add properties and methods to this object later.
const literalObject = {};
typeof literalObject;
Do not confuse this with JSON (JavaScript Object Notation).
- JSON only permits property definition using “property”: value syntax. The property name must be double-quoted, and the definition cannot be a shorthand.
- JSON values can only be strings, numbers, arrays, true, false, null, or another (JSON) object.
- A function value can not be assigned to a value in JSON.
- Objects like Date will be a string after
JSON.parse()
. JSON.parse()
will reject computed property names and an error will be thrown.
Following is not JSON but a valid object created using object literal notation
const notJSONObject = {
"property" : "This is a property.",
"method": function(){}
};
If you try to do a JSON.parse()
for notJSONObject
you will receive an error.
Using Object.create()
Second way to create an object in JavaScript is to use Object.create()
. This method needs a prototype of the object you want to create.
const emptyObject = Object.create(Object.prototype);
Above line creates an empty object. Additionally, the object created can be “initialized” with some properties while creating it.
const emptyObject = Object.create(Object.prototype, {
"property": {
writable: true,
configurable: true,
value: "A property."
},
"method": {
writable: false,
configurable: false,
value: function() {
return "A method.";
}
}
});
console.log(emptyObject.property);
console.log(emptyObject.method());
Above example creates an empty object and initializes it with some “data”. The “data” in this case is a properties object.
Using new operator
This is another way to create objects and is mostly used pattern after object literal notation method. The third way, also called constructor pattern, creates an object of the specific type of object whose constructor is used with a new operator. To better understand look at the example below
const dateObject = new Date();
The code above creates a Date object and has all the properties and methods of a date object.
dateObject.getDate();
Above line depending upon your system’s date time settings will return the current date on your system. When the code new Date()
is executed, the following things happen:
- A new Date object is created, inheriting from the Date prototype.
- The constructor function Date is called with the specified arguments and this is bound to the newly created object. Result of
new Date
is equivalent tonew Date()
, i.e. if no argument list is specified. However, the objects will not be same. Expressionnew Date() === new Date;
will result in false. - The object returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn’t explicitly return an object, the object created in step 1 is used instead.
Empty objects are of no use. Let us try to add some methods and properties.
There are 4 ways to add properties to an object. Before we move ahead, let us be clear what exactly a property of an object is? A variable associated with an object is commonly known as a property. A method on the other hand is a function associated with an object. For example in JavaScript, Arrays behave like an object.
const fruits = ["apple", "banana", "cherry", "date"];
console.log(fruits.length);
fruits.push("elderberry");
In the example above, fruits
is an array. It has a property length
which tells us its size or number of elements it has. Another property shown in example is a push()
which is actually a method to push elements into fruits array. Array object have many other methods and properties.
Be careful while creating arrays. Arrays can be created using new Array()
and []
bracket syntax. Both look similar but may work differently.
const squareBracketsArray = [2];
const newArray = new Array(2);
const anotherArray = new Array(2, 3);
console.log(squareBracketsArray.length);//prints 1
console.log(squareBracketsArray);//prints [2]
console.log(newArray.length); // prints 2
console.log(newArray); // prints [ , ], array with two empty slots
console.log(anotherArray.length); // prints 2
console.log(anotherArray); // prints [ 2, 3 ]
The new Array()
, if passed one argument, treats that argument as array length and creates an array with the given length.
Let us discuss ways to add properties to a JavaScript object.
Dot syntax
The dot operator in JavaScript is the simplest way to access properties of an object. In the last example we used dot syntax to push elements to an array. We used dot syntax to access length property. We can use the same dot syntax to add properties in an object.
const m = {};
m.avengers = ["Iron Man", "Thor", "Hulk" ];
const n = Object.create(Object.prototype);
n.justiceleague = ["Superman", "Batman", "Flash"];
const o = new Object();
o.xmen = ["Professor X", "Magneto", "Wolverine"];
In the example above, we created objects and added properties to them. In all three objects we added an array. We can also add methods to these properties
m.dummy = function() {
return "Hello World!";
};
console.log(m.dummy());
The dot syntax is simpler and easy to read, and programmers coming from other programming languages find it very familiar.
Square bracket syntax
The square bracket syntax is another way to add properties to an object.
const m = {};
m["avengers"] = ["Iron Man", "Thor", "Hulk"];
const n = Object.create(Object.prototype);
n["justiceleague"] = ["Superman", "Batman", "Flash"];
const o = new Object();
o["xmen"] = ["Professor X", "Magneto", "Wolverine"];
In the example above, we created objects and added properties to them. In all three objects we added an array. We can also add methods to these properties
m["dummy"] = function() {
return "Hello World!";
};
console.log(m.dummy());
The above example seems exactly the same as dot syntax. So what is the advantage of square bracket syntax?
Following is not possible with dot syntax
const p = {};
for (let i = 0; i < 10; i++) {
p["item_" + i] = i;
console.log(p["item_" + i]);
}
// Another example
const superHeroEmoji = "🦸♂️";
const superHerosObjects = {
[superHeroEmoji]: `${superHeroEmoji} is a super hero emoji`
}
Basically square bracket notations allow access to properties containing special characters and selection of dynamic property names.
Object.defineProperty way
The Object.defineProperty()
method defines a new property directly on an object, or modifies an existing property on an object, and returns the object. Object.defineProperty()
is ECMAScript 5 compliant. Older browsers (<IE9) do not support this. But almost all modern browsers support this. The basic syntax is as following:
Object.defineProperty(object, propertyname, property_descriptor)
The very first argument is the object for which we will be either adding or altering properties. The 2nd argument is the name of the property. And the third one is an object called property descriptor.
Object.defineProperty()
allows a very fine grained control over a property. This fine grained control is achieved through the property descriptor. Property descriptor object must have following two keys:
configurable
Allows the property to be configured later or even deleted. If you do not want this property to be altered using the property descriptor later on, you set this to false. Default is false
. Setting this will even prohibit deleting this property.
enumerable
Allows the property to be enumerated. You can find the property using for x in object
way. Default is false
.
Apart from these two mandatory properties in the descriptor object, the object will also have other properties which are further classified as data descriptor and accessor descriptor. As evident from the names these are related to data and access specifications for the properties. Let us create an object and notice how this works.
const batman = {};
const superhero = Object.create(Object.prototype);
Object.defineProperty(batman, "realname", {
configurable: false, //we do not want anyone to modify this property
enumerable: true, //must be visible when iterating the properties
//data descriptors
writable: false, //no one should be able to change bruce wayne to tony stark
value: "bruce wayne"
});
Object.defineProperty(superhero, "realname", {
configurable: true, //not sure what this hero will be, let us configure later
enumerable: true,
//access descriptors
get: function() {
return realname;
},
set: function(value) {
realname = value;
}
});
batman.realname = "tony stark"; //will this change realname for batman?
console.log("batman is actually " + batman.realname); //prints bruce wayne
In strict mode, any attempt to modify a non configurable value will throw an error. Also note that for a non configurable property, the writeable can be only false.
superhero.realname = "tony stark";
console.log("superhero iron man is actually " + superhero.realname);
Let us modify an existing property of an object.
const mySuperHero = function() {
const name, realname;
Object.defineProperty(this, "name", {
configurable: true, //not sure what this hero will be, let us configure later
enumerable: true,
//access descriptors
get: function() {
return name;
},
set: function(value) {
name = value;
}
});
Object.defineProperty(this, "realname", {
configurable: true, //not sure what this hero will be, let us configure later
enumerable: true,
//access descriptors
get: function() {
return realname;
},
set: function(value) {
realname = value;
}
});
};
const superman = new mySuperHero();
superman.name = "superman";
superman.realname = "clark kent";
console.log("my superhero is " + superman.name);
console.log("my superhero's alternate identity is known as " + superman.realname);
Here is an explanation for the properties of descriptor object
writable
A data descriptor property, default value is false
. Set to true
if you want to change the values. In our case we didn’t want anyone to set Batman’s name to Tony Stark. Everyone knows Bruce Wayne is batman.
value
The actual value of property. If not set the value is undefined.
get
A getter method to get the value of the property. This property is part of the access descriptor object.
set
A setter method for setting the value of the property. This property is part of the access descriptor object.
What happens when you try to set both data and access descriptor properties? JavaScript will throw an error.
A property cannot both have accessors and be writable or have a value
You cannot have both data and access descriptors for an object.
Object.defineProperties way
The Object.defineProperties()
method defines a set of properties directly on an object, or modifies existing properties on an object, and returns the object. The syntax is as following:
Object.defineProperty(object, propertyname, property_descriptor)
The very first argument is the object for which we will be either adding or altering properties. The 2nd argument is an object with keys being the name of properties and the value for these keys being property descriptors. Object.defineProperty()
will only deal with one property at a time but Object.defineProperties()
lets us modify or add more than one property.
const superhero = {};
Object.defineProperties(superhero, {
name: {
configurable: true,
enumerable: true,
//data descriptors
writable: true
},
realname: {
configurable: true,
enumerable: true,
//access descriptors
get: function() {
return realname;
},
set: function(value) {
realname = value;
}
}
});
superhero.name = "batman";
superhero.realname = "bruce wayne";
console.log(superhero.name);
console.log(superhero.realname);
While using Object.defineProperty()
and Object.defineProperties()
following must be known
- Both accept property descriptor objects.
- Property descriptor object can not have both data and accessors.
- In case a property exists, both methods try to modify the property with a new descriptor while taking care of existing descriptor values. In case the original properties descriptor had a configurable set as false, modifying the property will result in failure.
- In case a property descriptor object doesn’t have a property set, that property will be set to default value. In the last example we didn’t set any value for superhero.name property. The default in this case will be undefined and superhero.name till the time is not set will be undefined. If we had omitted the writable as well, we will not be able to set value for this property. It is advisable to set proper values for descriptor objects.
Accessing and Iterating over properties
Having super hero objects doesn’t serve a purpose if we do not use them. As with defining there are ways to access and iterate properties. Like setting a property, we can use dot and square bracket ways to access properties.
const superheroes = {
batman: {
realname: "bruce wayne",
livesin: "gotham city"
},
superman: {
realname: "clark kent",
livesin: "metropolis"
},
};
console.log(superheroes.batman);
console.log(superheroes.superman.realname);
console.log(superheroes["superman"].livesin);
As pointed out earlier, square brackets allow us to have variables as keys. This comes in handy when we do not know the exact name of a key. For example using JSON data or a multidimensional array we might not know all the keys in the object.
//Pre ES6 way
for (var i in superheroes) {
console.log("my superhero is " + i);
console.log("my superhero\"s real name is " + superheroes[i].realname);
}
//With ES6 in picture
Object.keys(superheroes).forEach((element) => {
console.log("my superhero is " + element);
console.log("my superhero's real name is " + superheroes[element].realname);
});
Both for…in
and Object.keys()
return an array of object’s enumerable properties. The order is the same in both cases. The only difference you will notice is that for…in
also enumerates properties of prototype chains. And with the advent of ES6, for…in
is not a desirable way to work with objects.
But what happens if we had set the property using Object.defineProperty()
and the property descriptor object has set the enumerable as false
? That property doesn’t appear when we try to enumerate object using for…in
or Object.keys()
. But you can still access the key. Why would you want some property to be not enumerable? This saves you from worrying about using hasOwnProperty
.
Objects can have complex structures and using for…in
is not the only way to look for properties in an object. We can use the Object.keys()
method to iterate through all the enumerable keys of an object. Object.keys()
returns an array-like object with string elements as keys of the object.
const superheroes = {
batman: {
realname: "bruce wayne",
livesin: "gotham city"
},
superman: {
realname: "clark kent",
livesin: "metropolis"
},
spiderman: {
realname: "peter parker",
livesin: "new york"
}
};
console.log(Object.keys(superheroes));
console.log(Object.keys(superheroes.spiderman));
There are some differences from the for…in
a way here. Object.keys()
doesn’t list properties from the prototype chain. The order of elements in the returned array is the same as if it was iterated through a for…in
loop.
const ignTopSuperheroes = {
2: "batman",
3: "spiderman",
1: "superman",
4: "wolverine",
5: "wonder woman"
};
for (let key in ignTopSuperheroes) {
console.log(key + ": " + ignTopSuperheroes[key]);
}
console.log(Object.keys(ignTopSuperheroes));
In the above example the order of the keys is identical.
Another method to get all the properties of an object, enumerable or not, is Object.getOwnPropertyNames()
. Object.getOwnPropertyNames()
returns an array-like object with all the properties of an object. It will not list the inherited properties of object.
const complexObject = Object.create({}, {
enumerableProperty: {
enumerable: true,
value: "enumerable property"
},
nonEnumerableProperty: {
enumerable: false,
value: "non enumerable property"
},
anotherProperty: {
value: "non enumerable property, default param for enumerable is false"
},
});
//following logs all the properties of complexObject
console.log(Object.getOwnPropertyNames(complexObject));
//following logs only the enumerable properties of complexObject
console.log(Object.keys(complexObject));
What is the best way to iterate over properties of an object? Tricky question. It all depends on the situation.
As stated earlier, in JavaScript even arrays behave like an object. Their indexes are more like enumerable properties, they just happen to be numerical property names. for…in
doesn’t guarantee the order of elements will be maintained for an array. When iterating over an array, for…in
is a bad choice. Apart from this for…in
also iterates over object properties which it might have inherited from parent objects. If you do not want properties from parent object inherited through the prototype chain you can use Object.keys()
.
function Superhero(superheroname) {
this.superheroname = superheroname;
this.altid = "";
this.livesincity = "";
this.superpowers = [];
}function MarvelSuperhero() {
this.isavenger = false;
}function DCSuperhero() {
this.isjusticeleaguehero = false;
}
MarvelSuperhero.prototype = new Superhero();
DCSuperhero.prototype = new Superhero();
const superman = new DCSuperhero("superman");
superman.isjusticeleaguehero = true;
const spiderman = new MarvelSuperhero("spiderman");
//All the properties of superman object
for (let key in superman) {
console.log(key);
}
//only the "own" properties of spiderman object
console.log(Object.keys(spiderman));
In case you are using for…in
and only want properties of the object not its parents you can use Object.prototype.hasOwnProperty()
. Modifying the above example.
function Superhero(superheroname) {
this.superheroname = superheroname;
this.altid = "";
this.livesincity = "";
this.superpowers = [];
}function MarvelSuperhero() {
this.isavenger = false;
}function DCSuperhero() {
this.isjusticeleaguehero = false;
}
MarvelSuperhero.prototype = new Superhero();
DCSuperhero.prototype = new Superhero();
const superman = new DCSuperhero("superman");
superman.isjusticeleaguehero = true;
const spiderman = new MarvelSuperhero("spiderman");
//Only the own properties of superman object
for (let key in superman) {
if (superman.hasOwnProperty(key)) {
console.log(key);
}
}
//Only the own properties of spiderman object
console.log(Object.keys(spiderman));
Object.prototype.hasOwnProperty()
only looks for the direct properties of an object.
Deleting properties
JavaScript provides a delete operator to delete properties of an object.
"use strict"; //strict mode
//A simple object
const DCSuperheroes = {
batman: {
realname: "bruce wayne"
},
spiderman: {
realname: "peter parker"
},
superman: {
realname: "clark kent"
}
};
console.log(DCSuperheroes);
// Spiderman is not part of the DC Universe"s Justice League, we need to delete it. We can delete this property in either of the following way
delete DCSuperheroes.spiderman;
//square bracket way to delete a property
delete DCSuperheroes["spiderman"]; //
console.log(DCSuperheroes);
But you cannot delete a property that is not configurable.
"use strict";
const MarvelSuperheroes = Object.create({}, {
batman: {},
spiderman: {},
});
//But Batman is not a Marvel character, let us delete it.
delete MarvelSuperheroes.batman;
Above code will throw an exception because its being run in strict mode. Even if it was not in strict mode you can not delete the batman property of MarvelSuperheroes object. While creating the object we used default settings in the property descriptor object which means the properties are not configurable at a later stage. This behavior is useful where you may not want certain properties of your object to be deleted at any point. Another point to remember is delete doesn’t free up memory directly. The delete operator returns false or throws an exception when in strict mode in case it can not delete a property and in all other cases it will return true. Running the above examples once again.
const DCSuperheroes = {
batman: {
realname: "bruce wayne"
},
spiderman: {
realname: "peter parker"
},
superman: {
realname: "clark kent"
}
};
if (delete DCSuperheroes.spiderman) {
//This can be confusing.
console.log("Unable to delete batman!");
}
const MarvelSuperheroes = Object.create({}, {
batman: {},
spiderman: {},
});
if (!delete MarvelSuperheroes.batman) {
console.log("Unable to delete batman!");
}
What happens when you try to delete a non existing property? You will be surprised.
"use strict";
const superheroes = {
DCSuperheroes: ["superman", "batman"]
};
console.log(delete superheroes.MarvelSuperheroes); //logs true
The delete operator has no effect on functions or variable names. You can not delete anything that you create using var
operator. Although variables are in turn properties of an object and properties can be deleted.
The delete operator only deletes the “own” property of the object. If the parent object has the same property and you delete a property from the child object, the child object will inherit the parent object’s property. A small example will help you understand this.
"use strict";
const superhero = {
realname: "",
liveson: "earth"
};
//Create a superman object by extending "superhero" object
const superman = Object.create(superhero, {
liveson: { //let us set a property
configurable: true, //so we can delete this property
value: "krypton"
}
});
console.log(superman.liveson); //krypton
console.log(delete superman.liveson); //krypton doesn"t exist any more
console.log(superman.liveson); //now inherits parent object"s property
In the above example, the superman
object has a property named liveson
which we delete. Our superhero lives on planet Earth now. The delete operator only deletes the property of the referenced object.
Given all these, delete operator should be used wisely. For someone coming from languages that allows them to do memory management, delete operator sounds like a way to manage memory or do garbage collection. As stated earlier, delete doesn’t do garbage collection directly. JavaScript will handle the garbage collection itself when a variable goes out of scope. Limit delete operator for just deleting properties, not objects. Most of the time, setting properties to null should be OK. You can then check for null and proceed accordingly. It should be clear that null and undefined are two different things. The typeof operator returns an object for null and undefined is undefined. When you check for null, you are sure that you have manually set the property to null and that property exists and has been initialized to null. In case of checking for undefined, that property may not even exist.