Objects - Dynamic HTML5 Web Applications with JavaScript and jQuery - HTML5, JavaScript and jQuery (Programmer to Programmer) - 2015

HTML5, JavaScript and jQuery (Programmer to Programmer) - 2015

Part II Dynamic HTML5 Web Applications with JavaScript and jQuery

Lesson 14 Objects

Most of the JavaScript data types you have looked at so far have held simple atomic values such as strings or numbers. This lesson looks at objects: Objects encapsulate multiple data properties, along with a set of methods capable of operating on these properties.

Objects are potentially the most difficult aspect of JavaScript for programmers migrating from other languages because objects in JavaScript work in a fundamentally different way than most other languages. As you will see, this is not a bad thing, but if you don't understand these fundamental differences, you will struggle to write complex web applications.

Object Literals

Objects can be created in JavaScript by enclosing a set of properties and methods within a pair of curly brackets. The following is an example of an object with two properties and one method:

> o = {

firstName:'Dane',

lastName:'Cameron',

getFullName: function() {

return this.firstName + ' ' + this.lastName;

}

}

In this case, the two properties are firstName and lastName. These properties are both strings, but they could be any data type: numbers, Booleans, arrays, or other objects.

Notice that the property names are separated from their values with colons, and the properties are separated from one another with commas.

It is possible to access these properties in two different ways:

> o.firstName

"Dane"

> o['firstName']

"Dane"

These two mechanisms are not exactly equivalent. The second mechanism (with the square brackets) will always work, whereas the first mechanism will only work if property names follow specific rules:

· They start with a letter, the underscore character, or the dollar sign.

· They only contain these characters or numbers.

For instance, it is possible to create an object as follows:

> o = {

'first name':'Dane',

'last name':'Cameron'

}

Because the property names contain spaces, however, they must be declared between quotes, and the only way to access these properties is with the square bracket notation:

o['first name']

"Dane"

The method on this object is getFullName. You will notice that this accesses the properties on the object with the keyword this. Inside a method, this refers to the object itself so this.firstName means that you want to access the firstName property of the object, not the variable called firstName (which would be undefined). It is possible to invoke the method as follows:

> o.getFullName()

"Dane Cameron"

Methods can therefore be thought of as functions that use the special value this differently.

After an object has been constructed, it is still possible to add additional properties or methods to it, or redefine any existing properties or methods. For instance:

> o.profession = "Software Developer";

> o.getFullName = function() {

return this.firstName + " " + this.lastName + " (" + this.profession + ")";

}

> o.getFullName();

"Dane Cameron (Software Developer)"

Notice that the call to getFullName picks up the redefined implementation, even though the object was created when the redefinition occurred.

It is possible for two variables to refer to the same object, but in this case, any changes to the objects are reflected to both variables. For instance, I can create a new variable called o2 and set it to o; calling getFullName will return the same value as calling the method on o:

> o2 = o;

> o2.getFullName();

"Dane Cameron (Software Developer)"

because o and o2 are referring to exactly the same object.

Prototypes

One of the main reasons programming languages use the concept of objects is to allow code reuse. It is common to have many objects that share the same properties and methods but with different property values.

For instance, the example in the previous section may represent a staff member in an employee management system; you may therefore create many similar objects, all with the same property names and methods, but each with distinct data in their properties. As a result, all these objects can share the same methods. Obviously, you could just add the relevant methods to each object you create, but this would become tedious.

If you have used languages such as Java or C#, you probably think of classes as the mechanism for acquiring this reuse. Classes are templates for objects, and many languages insist that you construct classes first, and then create objects from those classes. The classes therefore contain the methods and property names that will appear in the objects, but each object has its own values for the properties.

As you will see shortly, JavaScript does support syntax for creating objects in this manner, but it is not the core mechanism for code reuse in JavaScript. Instead, JavaScript is designed around the concept of prototypes.

As it turns out, every object in JavaScript has a prototype on which it is based, and it derives properties and methods from this prototype. In order to convince yourself of this, enter the following into the console:

> o = {};

> o.toString()

"[object Object]"

In this example, you create an empty object, with no properties or methods, and then invoke a method on it called toString. The toString method comes from the new object's prototype, which happens to be called Object.

A prototype is just a regular object in its own right. When a property or method is accessed on an object, JavaScript first tries to access it on the object itself. If it is not available there, it attempts to access it on the object's prototype. In fact, as you will see shortly, the object's prototype may have a prototype of its own; therefore, there can be a whole chain of prototypes. If the property or method still cannot be found after searching the prototypes, the value of undefined is returned.

Because many objects share the same prototype, adding functionality to prototypes provides a mechanism for code reuse.

Consider the case of an array in JavaScript. Every array that is constructed in JavaScript has a prototype object called Array: This is where the methods such as pop, map, and reduce are defined. Array itself has a prototype of Object, and this provides additional methods.

Because a prototype is just a regular object, you can add additional methods to it. These methods will then automatically be available to all arrays, even arrays created before you added the method.

For instance, you might decide you would like arrays to support a method called contains. This would accept an argument and return true if this existed as an element in the array. You can define this as follows:

> Array.prototype.contains = function (val) {

for (var i = 0; i < this.length; i++) {

if (this[i] === val) {

return true;

}

}

return false;

}

Array, in this case, is a constructor function (you will look at these shortly), whereas Array.prototype allows you to access the object that acts as the prototype to all arrays. Notice that within the method you add, you can use this to refer to an array itself.

You can then write code as follows:

> a1 = [1,5,3,8,10]

[1, 5, 3, 8, 10]

> a1.contains(8)

true

> a1.contains(9)

false

Prototypes can also solve the code reuse problem discussed earlier with the staff member objects. You can construct a single object that will act as the prototype of all staff member objects, and then set this as the prototype of any staff member object you construct.

You will start by defining an object with methods on it to act as the prototype:

staffPrototype = {

increasePay : function(percentage) {

this.salary = this.salary + ((this.salary * percentage) / 100);

},

getFullName : function() {

return this.firstName + " " + this.lastName + " (" + this.profession + ")";

}

}

Notice that this accesses four properties: firstName, lastName, salary, and profession. None of these properties has been defined on the object itself; therefore it is not possible to call these functions and have meaningful results returned. Despite this, the object definition is still considered valid by JavaScript.

You now need a mechanism to set this object as the prototype for other objects. The best way to do this is with the following function:

> function extend(obj) {

function T(){};

T.prototype = obj;

return new T();

}

You will use this function first and then come and look at how it works. Start by creating a new object with the following call:

> s1 = extend(staffPrototype);

Now add the relevant properties to s1:

> s1.firstName = 'Morgan';

> s1.lastName = 'Thomas';

> s1.salary = 50000;

> s1.profession = 'Graphic Designer';

Now, you should be able to use the methods added to the prototype and have these methods use the properties in your newly constructed object:

> s1.getFullName()

"Morgan Thomas (Graphic Designer)"

> s1.increasePay(10)

> s1.salary

55000

You can construct as many objects as you like from this same prototype:

> s2 = extend(staffPrototype);

> s2.firstName = 'Sam';

> s2.lastName = 'Donaldson';

> s2.salary = 60000;

> s2.profession = 'HR Manager';

All of these objects will use the methods you defined on the prototype but will have their own distinct values in each of the properties.

Although this clearly works, the extend function is rather mysterious. The first line of this function defines another function called T. Although this is a normal function, as you will see, it will be used as a constructor function, just as Array was.

Constructor functions are normal functions, but they are intended to be invoked with the new keyword. You will look at these in-depth in the next section. When they are invoked with the new keyword (as you can see on the third line), they implicitly return a new object.

The second line of the function is where all the magic happens however: On this line, the object passed into the function (staffPrototype in this case) is set as the prototype of the constructor function. This means that any objects constructed from this function will have this object set as their prototype.

Finally, it's important to understand that prototypes are read-only. For instance, the following code might be executed on one of the objects to redefine the getFullName method:

> s1.getFullName = function() {

return this.lastName + ", "+ this.firstName + " (" + this.profession + ")";

}

This code does succeed, but it only has the effect of providing a new definition of the method for the s1 instance of the object. The underlying prototype is not affected, and any other objects based on this prototype will also be unaffected.

Constructor Functions

The previous section briefly touched on the subject of constructor functions. These are the closest JavaScript has to classes because they provide a mechanism to construct new objects, and initialize their properties, all in a single step.

For instance, the following is a constructor function for initializing objects with the four properties used in the previous section:

function Staff(firstName, lastName, salary, profession) {

this.firstName = firstName;

this.lastName = lastName;

this.salary = salary;

this.profession = profession;

}

There is really nothing special about this function other than the following:

· It starts with a capital letter, when all the other functions and methods you have written start with lowercase letters. This is a convention to remind you that this function is a constructor function: You will see why this convention is important shortly.

· The body of the constructor function uses this to refer to a number of properties. As you will see, constructor functions implicitly return a new object, and properties can be set on the new object using this.

It is now possible to construct an object using this constructor function as follows:

s3 = new Staff('Brian', 'Downing', 40000, 'Software Tester');

This will create a new object and set its properties according to the arguments passed to the function.

This only constructs and initializes an object because it is invoked with the new keyword. If this were omitted, the function would still succeed, except it would not construct a new object:

> s4 = Staff('Brian', 'Downing', 40000, 'Software Tester');

undefined

As you can see, the function now returns the value of undefined. You may be wondering what happened to the calls inside the function such as this.salary. If a function is invoked without the new keyword, this refers to the JavaScript global namespace, which is thewindow object. The values passed to the function have therefore been created as global variables, overwriting any other global variables with the same name in the process:

> firstName

"Brian"

> salary

40000

Note

It is possible to use a stricter mode of JavaScript with the following declaration: “use strict”; When using strict mode this is undefined inside a function, and forgetting the new keyword would result in an error. Strict mode also addresses many of the other quirks we have seen with JavaScript.

This is why it is important to name constructor functions with leading capital letters: to remind yourself that they are constructor functions, and ensure that you precede them with the new keyword.

Note

If you are keeping count, you may have noticed that this has four different meanings, depending on the context in which it is used.

· Inside a regular function, this refers to the global namespace, which in browsers is the window object.

· Inside methods, this refers to the object the method is defined on.

· Inside constructor functions, this refers to the implicitly constructed object, but only when the function is invoked with the new keyword.

· When bind is used, this refers to the object passed as an argument.

Misunderstanding the meaning of this in each of these contexts is a common source of bugs in JavaScript code.

Modules

Most programming languages that support objects support a mechanism for controlling how data is accessed. For instance, consider the salary property from the objects in the previous section. Any code that has access to an object can set it to any value it wants, as you can see here:

s1.salary = 100000000;

In a real-world application, you may want to control the values that salary can be set to. For example:

> s1.updateSalary = function(newSalary) {

if (newSalary > 0 && newSalary < 200000) {

this.salary = newSalary;

} else {

throw 'The salary must be between 0 and 200000';

}

}

Although it is possible to expose methods such as this, this does not stop code from accessing the object's properties directly.

This may not sound like an important issue because you have full control over the code base, and you can therefore check that no one updates the salary property directly. This becomes increasingly difficult as the code base grows, however, and you introduce more and more rules about how properties should be accessed and updated.

Fortunately, there is a solution to this problem, and it relies on closures. The following is an example:

function createStaffMember(initialSalary, firstName, lastName) {

var salary = null;

o = {

setSalary : function() {

if (initialSalary > 0 && initialSalary < 200000) {

salary = initialSalary;

} else {

throw 'The salary must be between 0 and 200000';

}

},

getSalary : function() {

return salary;

},

firstName : firstName,

lastName : lastName

};

o.setSalary(initialSalary);

return o;

}

Notice that this function declares a local variable called salary and then constructs an object that uses this local variable. When the object is returned at the end of the function, it retains a reference to the salary variable so it is not destroyed. Despite this, there is no way any other code can set this variable without using the method setSalary, and this ensures the value is always within the acceptable range.

An object can be constructed from this function as follows:

> s5 = createStaffMember(50000, 'Tom', 'Braithwaite');

It may appear the salary property can be set as follows:

> s5.salary = 1000000000;

However, if you invoke the getSalary method, you will discover that the actual salary has not been modified:

> s5.getSalary();

50000

You will also notice that the object's methods do not access the salary variable with the this keyword. This is because salary is not a property of the object; it is a local variable the object has a reference to. Also notice that you need to provide a method (getSalary) for returning the current value of salary because there is no other way code outside the object could access this value.

The approach outlined in this section is a design pattern, which is a reusable solution to a well-known problem. This design pattern is referred to as the module design pattern and is used extensively in JavaScript programming.

Try It

In this Try It, you will use the module design pattern within the CRM web application. Although it will not do much at this point, it will provide a well-structured base on which to add additional functionality over the next few lessons.

Lesson Requirements

In order to complete this lesson, you will need the CRM web application as it stood at the end of Lesson 8. This can be downloaded from the book's website if you have not completed Lesson 8. You will also need a text editor and the Chrome browser.

Step-by-Step

1. Start by creating a standalone JavaScript file called contacts.js. This should be placed in the same folder as the contacts.html file.

2. Within this, start by creating a function called contactsScreen. This should accept a single parameter called mainID, and should return an empty object.

3. Define a local variable within the function (not within the object returned), called appScreen, and set this to the parameter passed into the function. You are going to pass the main element of the contacts.html page to this function when you eventually invoke it,

4. Create another local variable called initialized and set this to false.

5. Create a method inside the object returned called init. Add this method, and declare an empty code block for it. This is where you will place any logic that needs to execute when the web page first loads.

6. You want to make sure you only initialize the screen once. Therefore, at the top of the init method, check if initialized is true: If so, simply invoke return.

7. Copy the JavaScript code from contacts.js (minus the script tags: leave these in place), and add them to the body of the init method. In addition, set initialized to true at the end of the init method.

8. My completed version of the JavaScript file is available on the book's website.

9. You now need to link the JavaScript file to the HTML page to ensure it loads when the web page loads. In order to do this, add the following to the body of the head element:

<script src="contacts.js"></script>

9. Note

If you have used earlier versions of HTML, you may be expecting to add a type attribute to the script tag to specify the script is JavaScript. This is no longer required because JavaScript is the assumed default.

10.Now, pass the main element to the contactsScreen function and store the resulting object in a local variable called appScreen. This needs to occur inside the script block at the bottom of contacts.html.

11.Invoke the init method on the appScreen object. Your code block now looks as follows:

12. <script>

13. var mainElement = document.getElementById('contactScreen');

14. var appScreen = contactsScreen(mainElement);

15. appScreen.init();

</script>

12.If you now load the screen, you can add a breakpoint to the first line of the init method by selecting the contacts.js file from the Sources tab.

13.You can now reload the page and step through the init function to ensure it loads correctly.

Reference

Please go to the book's website at www.wrox.com/go/html5jsjquery24hr to view the video for Lesson 14, as well as download the code and resources for this lesson.