A JavaScript Object is a standalone container that holds multiple properties with values and methods (functions). An example is a customer object with firstName, lastName, and email properties, and a sendMessage method.
A JavaScript object is a collection of properties and methods similar to Hash, Map, or Dictionary in other languages. The name of a property can be any string, including an empty string. The value can be any value, such as a string, boolean, number, and null, but it cannot be undefined. The object's properties can be defined even after you start using the object. But first, let's look at how we create objects in JavaScript.
The easiest way is to create a new object is with object literal notation which is bracketed by a pair of curly braces: {}. Properties and their values can be added as you go. In the example b elow we create an empty object, using object literal notation, and we are adding two properties after that:
var rect = {}; // creates an empty object
rect.width = 20;
rect.height = 10;
console.log(rect.width); // => 20
console.log(rect.height); // => 10
As an alternative you can immediately assign properties and their values in the literal notation.
var rect = { width: 20, height: 10 };
console.log(rect.width); // => 20
console.log(rect.height); // => 10
JavaScript objects are mutable, meaning you can modify their values.
var rect = { width: 20, height: 10 };
rect.width = 30; // => modify value
console.log(rect.width); // => 30
console.log(rect.height); // => 10
Property values are not limited to primitive types, like number or string; you can also add properties that are other objects, including functions. When a function is added to an object it is called a method.
var rect = { width: 20, height: 10 };
// add new object
rect.color = { red: 0, green: 255, blue: 128 };
// add new method
rect.getArea = function () {
return this.width * this.height;
};
console.log(rect.color.red); // => 0
console.log(rect.color.green); // => 255
console.log(rect.color.blue); // => 128
console.log(rect.getArea()); // => 200
You can define objects and all their member properties and methods in a single statement as object literal. Below we create a rectangle with two numeric properties, one object property, and a method.
var rect = {
width: 20,
height: 10,
color: { red: 0, green: 255, blue: 128 }, // object property
getArea: function () { // method property
return this.width * this.height;
}
};
console.log(rect.width); // => 20
console.log(rect.height); // => 10
console.log(rect.color.red); // => 0
console.log(rect.color.green); // => 255
console.log(rect.color.blue); // => 128
console.log(rect.getArea()); // => 200
In this section we look at accessing, retrieving, and deleting object properties.
Property values can be retrieved in one of two ways; dot notation and bracket notation. Below are examples of each:
var rect = { width: 20, height: 10 };
console.log(rect.width); // => 20 (dot notation)
console.log(rect["width"]); // => 20 (bracket notation)
Dot notation is used more often because it is easier to read and more compact. So when would you use bracket notation? Square brackets allow you to use property names that are not valid identifiers and don't work with dot notation, for example when they have spaces in them or start with a number. Also, bracket notation allows you to use property names that are variables. Examples of both are below:
var shape = {
"bounding box width": 20,
"bounding box height": 10,
side1: 5,
side2: 15,
side3: 25,
side4: 7,
side5: 12
};
// could not be done with dot notation
console.log(shape["bounding box width"]); // => 20
for (var i = 1; i < 6; i++) {
var prop = "side" + i; // variable property name
console.log(shape[prop]); // => 5, 15, 25, 7, 12
}
var property = "side1";
console.log(shape.property); // => undefined (dot notation does not work)
The last two statements are included to demonstrate that dot notation does not work with the property being a variable.
To get a list of property names from an object use the for-in loop.
var car = { make: "Toyota", model: "Camry" };
for (var prop in car) {
// => make: Toyota, and model: Camry
console.log(prop + ": " + car[prop]);
}
The for-in loop returns all members of the object, that is, all properties and methods. If you don't need certain members or data types, you can exclude these from enumeration using the typeof operator. In the example below we skip functions.
var car = {
make: "Toyota",
model: "Camry",
print: function () {
console.log(this.make + " " + this.model);
}
};
for (var prop in car) {
if (typeof car[prop] !== "function") {
console.log(prop); // => make, and model
}
}
Be aware that the order in which the properties are returned by a for-in loop is not guaranteed. If order is important you will need to manage your own list of properties (probably as an internal array).
Use the delete operator to remove a property from an object, like so:
var circle = { radius: 8 };
console.log(circle.radius); // => 8
console.log(delete circle.radius); // => true
console.log(circle.radius); // => undefined
Object literal notation, such as var x = {}
, is preferred if all you need is a single
object and there is no need for multiple instances. However, if you need multiple instances,
it is better to use a constructor function. Here is an example of a book constructor function.
function Book(isbn) { this.isbn = isbn; this.getIsbn = function () { return "Isbn is " + this.isbn; }; }
Properties, including methods, are assigned to the 'this' value in the function's body.
In the above example a property and a function are assigned. Also notice that this function
is capitalized (i.e. Book
); constructor functions are capitalized by
convention in JavaScript
To create a new object with this function you use the new operator followed by a function invocation. A function that is invoked this way is called a constructor function whose main purpose is to create and initialize a new object. Here we are creating a new book object:
function Book(isbn) {
this.isbn = isbn;
this.getIsbn = function () {
return "Isbn is " + this.isbn;
};
}
var book = new Book("901-3865");
console.log(book.getIsbn()); // => Isbn is 901-3865
When new Book()
is invoked, JavaScript creates a new empty Object and sets an internal
property which specifies that the new object's prototype is Book, that is, the newly created object
inherits the prototype of the function. It then passes the Book()
function two
arguments: the new object as this
(as a hidden parameter) and the "901-3865" as
isbn
. The function, in turn, sets the object's isbn
property to "901-3865"
and also adds the getIsbn()
method to the object. JavaScript returns the newly
created object to the caller which then assigns the new object to the book
variable.
Each time you invoke new Book()
, a new getIsbn
method is created which is a
rather inefficient because the method is the same for all book instances. A better approach is to let
all instances share a single method which can be accomplished by adding getIsbn
to the
prototype of Book rather than the Book function itself. Here is how this is done:
function Book(isbn) {
this.isbn = isbn;
}
Book.prototype.getIsbn = function () {
return "Isbn is " + this.isbn;
};
var book = new Book("901-3865");
console.log(book.getIsbn()); // => Isbn is 901-3865
As you can see, it works the same as when getIsbn
was not shared. This is a very common
object creation pattern. To really get to know JavaScript, we present this pattern and many others
in our unique Dofactory JS.
Click here for more information.
If you forget to use the new
keyword, your code will break without warning. There won't
be any compile-time or runtime warnings. See the example below where we call the instructor
function with new
and later without new
.
function Task() {
this.message = "Learning JS";
}
var t = new Task(); // includes new
console.log(t.message); // => Learning JS
console.log(window.message === undefined); // => true
var u = Task(); // new is omitted!
console.log(u.message); // => error (not displayed in runtime)
console.log(window.message === undefined); // => false
console.log(window.message); // => Learning JS
Calling a constructor function without new
is like calling an ordinary function.
The this
value in this call will not be bound to a new object. Instead,
it is bound to the global object. Notice that the function is adding the message
property
to the global object (i.e. window). This is often referred to as polluting the global namespace.
You can protect yourself against this mistake by including a check at the beginning of each constructor
function. Essentially, it checks if this is an instance of Task
: if it is not, then it
invokes new Task()
returning a true new instance.
function Task() {
if (!(this instanceof Task)) {
return new Task();
}
this.message = "Learning JS";
}
var t = Task(); // new is omitted
console.log(t.message); // => Learning JS
To learn more about avoiding common JavaScript errors, the Dofactory JS includes helpful guidelines about optimal object instantiation, object scopes, and object lifetimes through the use of design patterns and best practices. To learn more click here.
In JavaScript, the this
keyword provides an object a way to identify and examine itself.
Consider the example below:
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.getRadius = function () {
return this.radius;
}
var smallCircle = new Circle(5);
console.log(smallCircle.getRadius()); // => 5
var largeCircle = new Circle(100);
console.log(largeCircle.getRadius()); // => 100
We have two circles, a small one and a large one. Each knows the size of their radius because in the getRadius method this value refers to the object it called from.
However, this
can refer to a different object as well; it all depends on the execution context.
All JavaScript code runs in an execution context. You can imagine the execution context as the scope of a
function. A call to a function creates a new execution context. Also, in recursive functions a new context
is created each time a function makes a call to itself.
Code that is not inside a function executes in a global execution context in which this is bound to the
global object. The example below shows that in the global context the str
variable is the same
as this.str
.
var str = 'hello';
console.log(str); // => hello
this.str = 'world'; // this refers to the global object
console.log(str); // => world
The value of this
inside a function body is determined how the function is invoked.
The context is commonly furnished by the parent scope in which the function was invoked. Consider
the example below:
var circle = {
radius: 10,
getRadius: function () {
console.log(this === circle);
console.log(this.radius);
}
};
circle.getRadius(); // => true, 10
var anotherCircle = {
radius: 12
};
anotherCircle.getRadius = circle.getRadius;
anotherCircle.getRadius(); // => false, 12
When invoking circle.getRadius()
JavaScript establishes an execution context for the
function call and sets this
to the circle object. anotherCircle
does not
have a getRadius()
method, but can be copied from circle. Once copied and executed,
the value of this
refers to anotherCircle
which is why the first alert,
which tests for cicle, return false, because this
is not of type circle.
There is an easy way to determine the value of 'this' in a function. Look at the immediate left side of the invocation parentheses () and determine the object to which it belongs to. Let's explain this with an example. Below we have three names with different scope. The First name has global scope and is a property of the global object, the Middle name is a student object property and the Last name is a detail object property.
var name = 'First';
var student = {
name: 'Middle',
detail: {
name: 'Last',
getName: function () {
console.log(this.name);
}
}
}
var result = student.detail.getName;
result(); // => First (global scope)
student.detail.getName(); // => Last (detail scope)
In the first example, the left side of the parentheses is the variable result
which
belongs to the global object when invoked. So we get its name which is 'First'.
The second example, the left side of the parentheses is getName
, which belongs to
the detail
object, so we get its name which is 'Last'.
Unlike classical inheritance where classes inherit from classes, JavaScript objects inherit from other objects, called prototypes, in a prototypal inheritance system.
Each object in JavaScript has a prototype property that points to a prototype object. There is nothing special about prototype objects, they are regular objects. Objects inherit all properties and methods that are available on the prototype object. Prototype objects themselves have prototypes, which leads to a prototype chain. This process in which objects inherit properties from objects in a prototype chain is referred to as prototypal inheritance. This model is simpler, smaller, and with less redundancy than that of classical inheritance.
Prototypal inheritance works on the concept of differential inheritance in which each item in the prototype chain only augments the differences with its parent. In other words, properties on prototype objects do not change or override properties in the parent object.
Let's look at an example. First, we create an account
object with a bank
property and a getBank
method. This object will serve as the prototype for other
objects like savings
, checking
, etc.
var account = { bank: "Bank of America", // default value getBank: function() { return this.bank; } };
Setting prototypes to an object is done by setting an object's prototype attribute to a
prototype object. We can make this process a bit simpler by using a helper function, so let's
first create this function; we'll name it createObject
. What follows may seem a
bit convoluted, but once you understand prototypes in JavaScript things will clear up.
Furthermore, our Dofactory JS does a wonderful
job of explaining the details of prototypal inheritance which will help you in your journey
to becoming a true JavaScript rockstar. To learn more about this framework
click here. But now back to createObject
.
createObject
accepts a prototype object as an argument, named p
and returns
a new object (a function object really) whose prototype attribute is set to the passed in
prototype object. The prototype attribute is how JavaScript is able to follow an object's prototype
chain.
function createObject (p) { var F = function () {}; // Create a new and empty function F.prototype = p; return new F(); }
With this function in place, we now like to create a savings
object that inherits
the functionality of the account
object. First, we call our createObject
function and pass in the account. This returns a new object with account
as its prototype.
Next we add member properties and methods that are specific to savings accounts.
function createObject(p) {
var F = function () { }; // Create a new and empty function
F.prototype = p;
return new F();
}
var account = {
bank: "Bank of America", // just the default value
getBank: function () {
return this.bank;
}
};
var savings = createObject(account);
savings.accountHolders = [];
savings.getAccountHolders = function () {
return this.accountHolders;
}
savings.accountHolders.push("Jack Saver");
savings.accountHolders.push("Mary Saver");
console.log(savings.getAccountHolders()[0]); // => Jack Saver
console.log(savings.getAccountHolders()[1]); // => Mary Saver
console.log(savings.getBank()); // => Bank of America
savings.bank = "JP Morgan Chase";
console.log(savings.getBank()); // => JP Morgan Chase
After creating the savings account, we add a couple of account holders to the object. Next we see which bank is associated with these savings accounts; as expected it is "Bank of America". In the second to last statement we change the bank to "JP Morgan Chase", which is confirmed with the alert message. This confirms that the prototypal inheritance chain works as expected.
You can inherit from savings and add new properties and methods to a new parent object, and do this again and again. Inheritance can go many levels deep, but in order to not overcomplicate things, it is generally best to limit this to 2 or 3 levels. Of course, JavaScript will just continue searching the prototype chain until it hits the last object, the global object, and if the property or methods is not found, JavaScript return undefined. Lengthy inheritance chains will negatively affect performance, which is another reason to avoid deep hierarchies.
Prototypal inheritance is a highly memory efficient. A prototype is just a single object and derived object instances hold only references to their prototype. All replicated instances share a single copy of the members defined in the prototype object.
If you are coding to the EcmaScript 5 specification, then you don't need to write the
aforementioned createObject
function yourself. EcmaScript 5 has a built-in
Object.create()
method, which allows the creation of derived objects
just as we did with our own createObject
. Here is the code:
// EcmaScript 5
var account = {
bank: "Bank of America", // just the default value
getBank: function () {
return this.bank;
}
};
var savings = Object.create(account);
console.log(savings.bank); // => Bank of America
If you are an experienced object-oriented developer you know that programming a class-based language is impossible without understanding class based inheritance. Similarly, writing professional JavaScript requires a solid grasp of prototypal inheritance to be able to move beyond the simple JavaScript snippets that were common just a few years ago.
Our unique Dofactory JS provides the necessary information on prototypes and how to use these in your own projects by applying proven design patterns and best practices. Click here for more details.
Once you are familiar with prototypal inheritance it is actually easier to work with than classical inheritance. Its syntax is more tidy and simple. To customize your objects, you can quickly modify properties and methods at runtime without requiring a large amount of infrastructure code.