Coder Social home page Coder Social logo

javascript-objects's Introduction

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

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

top

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.

top

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.

top

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.

top

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.

top

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.

top

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.

top

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.

top

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.

top

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.

top

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.

top

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.

top

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.

top

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.

top

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';

top

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'

top

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).

top

Lesson Review

  1. What is the blueprint for an object called?
    • class
  2. What does JavaScript use as a starting object when constructing a new object?
    • prototype
  3. 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.

top

javascript-objects's People

Contributors

james-priest avatar

Stargazers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.