Creating JavaScript Objects
Taken from Chapter 6: Essential JavaScript & jQuery of Programming in HTML5 with JavaScript and CSS3 by Glenn Johnson.
I wrote this out along with code examples as part of my study material for passing the Object-Oriented JavaScript portion of Microsoft's Exam 70-480: Programming in HTML5 with JavaScript & CSS3 certification exam.
Code examples use Tape (tap-producing test harness for node and browsers) which is a lightweight TDD package.
npm install tape --save-dev
Table of Contents
- Creating JavaScript Objects
- Table of Contents
- JavaScript Objects - MS Approach
- Object-oriented terminology
- JavaScript's prototypal nature
- JavaScript's object-oriented caveat
- JavaScript object literal pattern
- Creating dynamic objects by using the factory pattern
- Creating a class
- Using the prototype pattern
- Debating the prototype/private compromise with getters
- Implementing namespaces
- Implementing inheritance
- Lesson Summary
- Lesson Review
JavaScript Objects - MS Approach
In JavaScript everything can be thought of as an object. This includes the six primitive data types:
String
,Number
,Boolean
Symbol
,null
,undefined
JavaScript will also include complex data structures such as:
Object
,Array
,Function
Math
,Date
,JSON
,RegExp
,Error
NaN
,Infinity
,-Infinity
Object-oriented terminology
In many object-oriented languages, you create a class, which is a blueprint for an object. Like a blueprint for a house, the blueprint isn't the house; it's the instructions that define the type of object that you'll be constructing.
By using a house blueprint, you can construct many houses that are based on the blueprint. Each house is an object of type house, also known as an instance of the house type.
In a baseball application, you might create a Player class (classes are normally capitalized) that has properties for first and last name, batting average, error count, etc. Code for creating a team might use the Player class to create nine Player objects, each having its own properties. Each time you construct a Player object, memory is allocated to hold the data for the player, and each piece of data is a property, which has a name and a value.
The three pillars of object-oriented programming are encapsulation, inheritance, and polymorphism.
- Encapsulation means internal details of the object are hidden and protected from access by only exposing public methods and properties to users of the object
- Inheritance means you can create a "is a" relationship between two classes, in which the child class automatically inherits everything that is in the parent class.
- Polymorphism means you can execute a function on the parent class, but the behavior changes because your child class has a function that overrides the function in the parent class.
The parent class is also known as the base class, the super class, or the generalized class. The child class is also known as the derived class, the subclass, or the specialized class.
In object-oriented programming, objects can have data implemented as properties and behaviors implemented as methods. A property is essentially a variable that is defined on an object and owned by the object. A method is a function that is defined on an object and owned by the object.
JavaScript's prototypal nature
JavaScript is a prototype-based, functional programming language that uses objects. In JavaScript everything is an object, and you either create a new object from nothing, or you create an object from a clone of an existing object, known as a prototype.
JavaScript wasn't designed to be class-based but this behavior can be approximated by using a function. Class-based, object-oriented purists dislike the idea of a function being used to simulate a class but this can work to approximate a class constructor.
JavaScript's object-oriented caveat
Achieving proper encapsulation of private data requires you to create copies of the functions that can access the private data for each object instance, which consumes memory. If you don't want to create copies of the method for each object instance, the data needs to be publicly exposed, thus losing the benefits of encapsulations, by which you hide object details that users shouldn't need to see.
The general consensus on the issue is that most people would rather expose the data to minimize wasteful memory consumption.
JavaScript object literal pattern
- Object literals create a pattern from nothing.
- They contain precisely what's assigned to them and nothing more.
- No prototype object is associated with the created object.
var car1 = {
year: 2000,
make: 'Ford',
model: 'Fusion',
getInfo: function() {
return 'Vehicle: ' + this.year + ' ' + this.make + ' ' + this.model;
}
};
var car2 = {
year: 2010,
make: 'BMW',
model: 'Z4',
getInfo: function() {
return 'Vehicle: ' + this.year + ' ' + this.make + ' ' + this.model;
}
};
View code: (a-object-literal.js)
The getInfo
property doesn't contain data; it references an anonymous function instead, so it's a method. The method uses the this keyword to access the data. Remember that the this keyword references the object that owns the code where the this keyword is. If the this keyword were omitted, the code would look in the global namespace for year, make, and model.
If you want to define an array of items and assign it to a property, you can use square brackets as shown below.
var car1 = {
year: 2000,
make: 'Ford',
model: 'Fusion',
repairs: ['repair1', 'repair2', 'repair3'],
getInfo: function() {
return 'Vehicle: ' + this.year + ' ' + this.make + ' ' + this.model;
}
};
Because this is one of the easiest ways to create an object, you'll probably use it to gather data to send to other code. In this example, two instances of a type Object are created, and properties are dynamically added to each instance. This does not create a Vehicle type.
Creating dynamic objects by using the factory pattern
In addition to using the JavaScript literal object syntax, JavaScript has an Object type, and you can use it to create an object programmatically. Object has a prototype object that is cloned when you use the new keyword to create a new Object instance. The prototype object has the following inherited methods.
- constructor The function that is called to initialize a new object
- hasOwnProperty Returns a Boolean indicator of whether the current object has the specified property.
car1.hasOwnProperty('year') -> true
. - isPrototypeOf Returns a Boolean indicator of whether the current object is in the specified object's prototype chain
- propertyIsEnumerable Returns true if the object can be enumerated in a for..in loop
- toLocalString Converts a date to a string value based on the current local
- toString Returns the string representation of the current object
- valueOf Returns the value of the current object converted to its most meaningful primitive value
After the object is created, you can dynamically add properties to it that hold the data and reference functions. You can wrap this code in a function that returns the object.
function getVehicle( theYear, theMake, theModel ) {
var vehicle = new Object();
vehicle.year = theYear;
vehicle.make = theMake;
vehicle.model = theModel;
vehicle.getInfo = function() {
return 'Vehicle: ' + this.year + ' ' + this.make + ' ' + this.model;
};
return vehicle;
}
View code: (b-object-instance.js)
The code takes advantage of JavaScript's dynamic nature to add year, make, model, and getInfo to the object and then returns the object. Placing the code in a function makes it easy to call the getVehicle function to get a new Object.
The encapsulation of the code to create an object is commonly referred to as using the factory pattern. You can create multiple instances of Object and add properties dynamically to each instance, but the actual type is Object
, not vehicle
. The variable name vehicle
is just used to hold the new Object
type before returning it from the getVehicle
function.
Additionally, although the getVehicle function encapsulates the object creation, the properties are all public. If you want the data to be private, this approach won't work.
Just as when using the literal object syntax, you might encounter the problem that every vehicle's type is Object, and you might want to create a Vehicle class to have a named Vehicle
type.
Creating a class
There is no class keyword in JavaScript (ES5), but you can simulate a class by starting with a function, which is actually the constructor function of the object.
function Vehicle( theYear, theMake, theModel ) {
...
}
The next step is to ensure we implement encapsulation. Then we need to create separate objects, each with its own data.
To implement encapsulation, use the var keyword for the year, make, and model. This will make these variables private to the function. Notice that the var keyword is not used with the getInfo
variable because the getInfo
variable needs to be public to be called from outside the object, but you don't want getInfo
to be global. Assign getInfo
to the current object by using the this keyword. The result is a class that encapsulates the data and exposes getInfo to retrieve the data in a controlled way as follows.
function Vehicle( theYear, theMake, theModel ) {
var year = theYear; // private
var make = theMake; // private
var model = theModel; // private
this.getInfo = function() { // public
return 'Vehicle' + year + ' ' + make + ' ' + model;
};
}
View code: (c-object-encapsulation.js)
Remember that the this keyword references the object that owns the current code. This means that we must use the new keyword to create an object from the class. Otherwise, the this keyword will reference the global object and getInfo
will be referring to a global variable.
var car1 = new Vehicle( 2000, 'Ford', 'Fusion' );
var car2 = new Vehicle( 2010, 'BMW', 'Z4' );
var carInfo = car1.getInfo(); // Vehicle: 2000 Ford Fusion
Notice that a new variable is defined, car1
, and it is assigned the object that is created by using the new keyword. After that, another new variable is defined, car2
, and it is assigned the second Vehicle
object created by using the new keyword.
Two instances of the Vehicle
class are being created, which means that two Vehicle
objects are being constructed. Each instance has its own data and its own copy of the getInfo
method. The getInfo
method is public but has access to the private data through closure. A method that is public but has access to private data is called a privileged method.
You have now created a class and constructed objects from the class. The Vehicle function you've used is known as a constructor function. The new keyword created an object and executed the constructor function to initialize the object by creating the year
, make
, and model
private variables and the public getInfo
variable.
Each instance has these four variables, and memory is allocated for them. That's what you want for the data but is that what you want for the getInfo
variable that references a function? The answer is that it depends on what you are trying to do.
Consider the following code that creates two Vehicle
objects but then replaces the code in getInfo
of the first Vehicle
object with different code. Does this replace the code in the second object?
function Vehicle( theYear, theMake, theModel ) {
var year = theYear;
var make = theMake;
var model = theModel;
this.getInfo = function() {
return 'Vehicle: ' + year + ' ' + make + ' ' + model;
};
}
var car1 = new Vehicle( 2000, 'Ford', 'Fusion' );
var car2 = new Vehicle( 2010, 'BMW', 'Z4' );
car1.getInfo = function() {
return 'This is a car';
};
car1.getInfo(); // 'This is a car'
car2.getInfo(); // 'Vehicle: 2010 BMW Z4'
View code: (d-method-replacement.js)
In some scenarios, this behavior is desirable, but in others, you might want to replace the function across all objects. To do this, you need to use the prototype pattern.
Using the prototype pattern
In JavaScript, everything, including the function, is an Object type, which has a prototype property. The prototype itself is an object containing properties and methods that should be available to all instances of the type you're working with.
However, this prototype is typically specified externally to the constructor function, so the prototype doesn't have access to private variables. Therefore, you must expose the data for the prototype to work.
The following is an example of using the prototype property to create a single getInfo
method that is shared across all instances.
function Vehicle( theYear, theMake, theModel) {
this.year = theYear; // public
this.make = theMake; // public
this.model = theMode; // public
}
Vehicle.prototype.getInfo = function() {
return 'Vehicle: ' + this.year + ' ' + this.make + ' ' + this.model;
}
View code: (e-prototype-function.js)
Remember, this exposes the variable as a public property on Vehicle
and var defines the variable as private.
You might use the prototype property when creating functions that will be shared across all instances, but remember that the prototype is defined externally to the constructor function so all properties MUST BE PUBLIC using the this keyword.
Therefore, if you don't need to replace individual instance functions and you don't mind making your data public, the prototype is efficient.
Debating the prototype/private compromise with getters
There can be a compromise in which you can have private data that is readable by creating a method for retrieving the data, also known as a getter. (This getter will have no setter.) This requires you to write a function that is copied for each object, but you should keep the function as small as possible as shown here.
function Vehicle( theYear, theMake, theModel ) {
var year = theYear; //private
var make = theMake; // private
var model = theModel; // private
this.getYear = function() { return year; };
this.getMake = function() { return make; };
this.getModel = function() { return model; };
}
Vehicle.prototype.getInfo = function() {
return 'Vehicle: ' + this.getYear() + ' ' + this.getMake() + ' ' + this.getModel();
};
View code: (f-prototype-getters.js)
Here we can replace the getInfo
method and, because the data is exposed as read-only, it's available to be used in the new method.
Vehicle.prototype.getInfo = function() {
return 'Car Year: ' + this.getYear()
+ ' Make: ' + this.getMake()
+ ' Model: ' + this.getModel();
};
In addition, the privileged getters are small, which minimizes the amount of memory consumed when each instance has a copy of the method. Remember to only create getter methods as needed and to keep them small and concise.
Implementing namespaces
One problem to watch for is the pollution of the global namespace. As your program gets larger and libraries are added, more entries are added to the global object.
JavaScript doesn't have a namespace keyword, but you can implement the equivalent of a namespace by using techniques that are similar to those used to create objects.
// Bad pollution of global namespace
var vehicleCount = 5;
var vehicle = new Array();
function Car() { }
function Truck() { }
var repair = {
description: 'changed spark plugs',
cost: 100
};
This code places five entries in the global namespace, and as the application grows, this namespace pollution also grows. You can implement the namespace pattern to solve the problem.
// Single entry in global namespace
var myApp = { };
myApp.vehicleCount = 5;
myApp.vehicles = new Array();
myApp.Car = function() { };
myApp.Truck = function() { };
myApp.repair = {
description: 'changed spark plugs',
cost: 100
};
View code: (g-global-namespace.js)
Here, myApp
is the only entry in the global namespace. It represents the name of the application and its root namespace. Notice that the object literal syntax is used to create an empty object and assign it to myApp. Everything else is added to the object. Sub-namespaces can also be created and assigned to myApp
.
You can see a namespace was created by creating an object. Although only one entry is made in the global namespace, all the members of myApp
are globally accessible.
Namespace singleton object
You might also want to have logic to create the namespace object only if it hasn't been created. In the following example, the code for myApp
is modified to create the namespace only if it doesn't already exist. This is done by creating a new object if myApp
does not have a value.
var myApp = myApp || {};
View code: (h-namespace-singleton.js)
You can use the object techniques defined earlier to make some members of the namespace private and some public. The difference is that the namespace is a singleton object, so you create a single instance for the namespace.
You don't need to worry about functions defined in the constructor function consuming additional memory for each instance because there is only one instance.
Creating namespace with IIFE for data encapsulation
Here is an example of the use of an immediately invoked function expression (IIFE) to create the myApp
namespace in which Car
and Truck
are public, but vehicleCount
, vehicles
, and repair
are private.
(function() {
this.myApp = this.myApp || {}; // singleton pattern
var ns = this.myApp;
var vehicleCount = 5; // private
var vehicles = new Array(); // private
ns.Car = function() {}; // public
ns.Truck = function() {}; // public
var repair = { // private
description: 'changed spark plugs',
cost: 100
};
})();
View code: (i-namespace-iife.js)
An IIFE (pronounced iffy) is an anonymous function expression that has a set of parentheses at the end of it which indicates that you want to execute the function. The anonymous function expression is wrapped in parentheses to tell the JavaScript interpreter that the function isn't only being defined; it's also being executed when the file is loaded.
In the above IIFE, the first line creates the myApp namespace if it doesn't already exist, which represents the singleton object that is used as the namespace. Next, an ns
variable (for namespace) is created as an alias to the namespace. This saves typing but most importantly creates a named "memory pointer" so the interpreter doesn't have to traverse the object chain each time we want to access a nested property. The result is ns
can be used in place of the this.myApp
. After that, the private members of the namespace are defined by using the var keyword. Car
and Truck
are public, so they are prefixed with ns
.
Creating a sub-namespace
The following example shows adding a billing
namespace under the myApp
namespace.
(function() {
this.myApp = this.myApp || {};
var rooNS = this.myApp;
rootNS.billing = rootNS.billing || {};
var ns = rootNS.billing;
var taxRate = 0.05;
ns.Invoice = function() {};
})();
This example also implements an IIFE to create the namespace. First, the myApp
namespace is created if it doesn't already exist and is assigned to a local rootNS
variable to save typing inside the namespace. Next, the billing namespace is created and assigned to the local ns
variable to save typing inside the namespace. Finally, the private taxRate
property is defined while the public Invoice
is defined.
Implementing inheritance
JavaScript provides the ability to implement inheritance, which is useful when you can define the relationship between two objects as an "is a" relationship. For example, and apple is a fruit, an employee is a person, and a piano is an instrument. You look for "is a " relationships because they provide an opportunity to implement code reuse.
If you have several types of vehicles, you can create Vehicle
with the common vehicle traits defined in it. After Vehicle
is created, you can create each vehicle type and inherit from Vehicle
so you don't need duplicate code in each vehicle type.
Base class
As an example of inheritance, start by defining the base case. Using the Vehicle
example, the following is an example of a Vehicle base class.
var Vehicle = (function() {
function Vehicle( theYear, theMake, theModel ) {
this.year = theYear;
this.make = theMake;
this.model = theModel;
}
Vehicle.prototype.getInfo = function() {
return this.year + ' ' + this.make + ' ' + this.model;
};
Vehicle.prototype.startEngine = function() {
return 'Vroom';
};
return Vehicle;
})();
View code: (j-base-class.js)
This class is wrapped in an IIFE. The wrapper encapsulates the function and the Vehicle prototype. There is no attempt to make the data private. The code works as follows:
- When the code is loaded into the browser, the IIFE is immediately invoked
- A nested function called Vehicle is defined in the IIFE
- The Vehicle function's prototype defined the getInfo and startEngine functions that are on every instance of Vehicle
- A reference to the Vehicle function is returned, which is assigned to the
Vehicle
variable
This is a great way to create a class, and all future class examples use this pattern.
To create Vehicle
objects, you use the new keyword with the Vehicle
variable. The following creates an instance of Vehicle
and invokes the getInfo
method.
var v = new Vehicle( 2012, 'Toyota', 'Rav4' );
var actual = v.getInfo();
var expected = '2012 Toyota Rav4';
Child class
Now that you have a Vehicle parent class with three properties and two methods, you can create child classes for Car
and Boat
that inherit from Vehicle
.
Start by writing an IIFE but, this time, pass Vehicle
into the IIFE as follows.
var Car = (function( parent ) {
...
})(Vehicle);
Because Vehicle in this example is the Vehicle
variable not the Vehicle function, Car
needs to be defined after Vehicle
. Vehicle
is passed into the IIFE and is available inside the IIFE as parent.
Next, the function for Car
can be added inside the IIFE. Inside the function, add any additional properties, such as wheelQuantity
, and initialize to four.
In the function, call the parent class's constructor for Car
to allocate memory slots for year, make, and model. To call the parent constructor function, use a call method that exists on the Function object, which accepts a parameter for the this
object, and parameters on the function being called, as follows.
var Car = (function( parent ) {
function Car( year, make, model) {
parent.call(this, year, make, model);
this.wheelQuantity = 4;
}
return Car;
})(Vehicle)
Notice how this example used the call method to modify the this
object; the this
object is the Car
object, so the call to the parent construction function creates year, make, and model on the Car
object.
Next, the inheritance must be set up. You might think that you've already set up inheritance because the previous example calls the parent class's constructor, and the year, make, and model are created on Car
, but getInfo
and startEngine
were not inherited.
The inheritance is accomplished by changing the Car
prototype object to be a new Vehicle
object. Remember that the prototype is the object that is cloned to create the new object. By default, the prototype is of type Object. After the new Vehicle
is assigned to the prototype, the constructor of that Vehicle
is changed to be the Car
constructor as follows.
var Car = (function( parent ) {
Car.prototype = new Vehicle(); // <-- here
car.prototype.constructor = Car; // <-- here
function Car( year, make, model ) {
parent.call( this, year, make, model );
this.wheelQuantity = 4;
}
return Car;
})(Vehicle);
Finally, you can add more methods into Car
. In this example, the getInfo method is added, which replaces the Vehicle
getInfo method. The new getInfo
gets some code reuse by calling the existing getInfo
method on the parent Vehicle
object's prototype. However, you must use the call method and pass the this
object as follows.
var Car = (function( parent ) {
Car.prototype = new Vehicle();
Car.prototype.constructor = Car;
function Car( year, make, model ) {
parent.call( this, year, make, model );
this.wheelQuantity = 4;
}
Car.prototype.getInfo = function() { // <-- here
return 'Vehicle Type: Car ' + parent.prototype.getInfo.call(this); // <-- here
}; // <-- here
return Car;
})(Vehicle);
View code: (k-inherited-class.js)
This completes Car
, and Boat
is similar except that Boat
has a propellerBladeQuantity
, which is initialized to three, instead of the wheelQuantity
property. In addition, getInfo
returns the vehicle type of Boat
and calls the Vehicle
getInfo
method as follows.
var Boat = (function( parent ) {
Boat.prototype = new Vehicle();
Boat.prototype.constructor = Car;
function Boat( year, make, model ) {
parent.call( this, year, make, model );
this.propellerBladeQuantity = 3;
}
Boat.prototype.getInfo = function() {
return 'Vehicle Type: Boat ' + parent.prototype.getInfo.call(this);
};
return Boat;
})(Vehicle);
View code: (k-inherited-class.js)
To create the Car
and Boat
objects, you use the new keyword with the Car
or Boat
variable. The following creates instances of both Car
and Boat
and invokes each of their object-specific methods.
var c = new Car( 2012, 'Toyota', 'Rav4' );
c.wheelQuantity; // 4
c.startEngine(); // 'Vroom'
var b = new Boat(1994, 'Sea Ray', 'Signature 200');
b.propellerBladeQuantity; // 3
b.startEngine(); // 'Vroom'
Lesson Summary
- A class is a blueprint for an object in which an object is an instance of a class.
- The three pillars of object-oriented programming are encapsulation, inheritance, and polymorphism.
- The class from which you inherit is called the parent, base, super, or generalized class. The class that is derived from the parent is called the child, derived, sub, or specialized class. You can implement inheritance by replacing the Child class prototype with a new instance of the parent and replacing its constructor with the Child constructor function
- JavaScript is a prototype-based, object-oriented programming language. A prototype is the object used to create a new instance.
- The literal pattern can be used to create an object by using curly braces to create the object. The factory pattern can be used to create a dynamic object.
- JavaScript does not have a class keyword but you can simulate a class by defining a function.
- Creating private members is possible but usually involves creating privileged getter methods that can be memory consuming.
- The function is an object. The function that simulates a class is called the constructor function.
- Namespaces can be created by using an immediately invoked function expression (IIFE).
Lesson Review
- What is the blueprint for an object called?
- class
- What does JavaScript use as a starting object when constructing a new object?
- prototype
- How is inheritance supported in JavaScript?
- You replace the prototype of the child object with a new instance of the parent object and then replace the prototype constructor with the child constructor.