Chapter 10. Getting Started with JavaScript

In the previous chapter, we covered how Struts 2 can use both application- and action-specific exception configurations to deal with application errors. We also looked at ways to enhance our exception classes by including application-specific information. Finally, we saw how to configure logging, so that we can see what's going on behind the scenes in both the framework and our own code.

In this chapter, we'll take a look at JavaScript—a common element of many web applications. Having a solid foundation in JavaScript and the Document Object Model (DOM) is critically important for developing reactive, functional web applications, and is particularly important when we start developing Ajax-based applications.

JavaScript is a much-maligned language, particularly by those without a grounding in other dynamic languages such as Lisp, Smalltalk, Self, and so on. JavaScript is an incredibly flexible and capable language when used in ways that play to its strengths. Applying non-JavaScript design patterns to JavaScript will produce inefficient code that is needlessly complex and difficult to debug.

We'll first look at some JavaScript gotchas, and will then explore some of the more advanced uses as seen in many JavaScript libraries, including a discussion of JavaScript's versions of object-oriented programming. We'll conclude by using JavaScript to create a more dynamic version of our ingredient list input fields, allowing an arbitrary number of ingredients to be entered.

Introduction to JavaScript

JavaScript has a syntax similar to C, C++, and Java. It is an object-oriented (OO) language, but not in the same way as Java. Understanding JavaScript's OO nature can bring a huge boost in productivity. While it's possible to program JavaScript in a "cookbook-style" manner (or worse, "code sample-oriented programming"), today's highly interactive applications can benefit from a deeper understanding of JavaScript.

While we won't cover the entirety of JavaScript for which there are other appropriate materials, we'll get a high-level overview of the language. We will then delve deeper into some of the more useful JavaScript and DOM patterns.

Playing with JavaScript

The easiest way to play around with JavaScript is to use Firefox combined with the Firebug plug-in. There are also some solutions for Internet Explorer if you're running on a platform that supports it. In this chapter's code, we'll sometimes assume we're in the Firebug console, allowing us to type in and execute code immediately. The rest of the code will be run inside a webpage. It will usually be obvious from the context.

Minor syntax and language notes

There are some minor syntax notes to be kept in mind, particularly some differences that can cause headaches to Java programmers.

Unicode

Modern JavaScript supports Unicode. Prior to ECMAScript v3, Unicode was allowed only in comments or string literals. Modern browsers (in theory) allow Unicode to be used anywhere, even in variable names. Personally, I'd avoid Unicode except in comments or string literals, but that may just be paranoia on my part.

Whitespace

Whitespace is ignored in JavaScript, except inside string literals. However, you can use the "" character to continue a line, including in the middle of string literals. This will not insert a new line in the string.

var s = "Hello, 
world!";
alert(s); // Will alert with the string "Hello, world!".

Semicolons

To make JavaScript easier, it was decided that semicolons at the end of statements should be optional. This can lead to some strange issues, as they can be inserted by the JavaScript compiler in places where we don't necessarily expect. The return, break, and continue statements are the primary culprits. The canonical example is the following code:

return
true;

This will be interpreted as:

return;
true;

This is almost never what we mean. The answer for this is to always use semicolons, and never rely on semicolon insertion.

Null and undefined values

JavaScript has both null and undefined values. Variables declared, but not initialized, are undefined, not null.

The equal and strict equal operators

Both == (equal) and === (strict equal), and their negated (using a prefixed !) counterparts may be used for equality operations. They behave differently. The equal operator does type conversion, the strict equal operator doesn't. We'll briefly examine type coercion later. In general, we'll usually want to use ===. Equal (==) and strict equal (===) also behave differently when comparing null and undefined values. With ==, null and undefined are equal, whereas with ===, they are not.

The logical OR operator

The || (logical OR) operator can be used to ensure a variable has a value.

var aVariable = anotherVariable || "Default value";

If the value of anotherVariable is null (or undefined), the value of aVariable will be set to"Default value", else it will be set to whatever there is in anotherVariable. This is a very common shorthand notation for the following code, and is seen a lot in good JavaScript code.

var aVariable;
if (anotherVariable == null) {
aVariable = "Default value";
} else {
aVariable = anotherVariable;
}

The same thing can be represented using the ?: operator as:

var aVariable = (anotherVariable == null) ? "Default value"
: anotherVariable;

We can also express this by using JavaScript's liberal notion of truthiness:

var aVariable = anotherVariable ? "Default value"
: anotherVariable;

Variables and scoping

Scoping in JavaScript is different from that in Java. One of the biggest differences is that undeclared variables (variables that don't use the var keyword) are automatically assumed to be global variables. This can cause endless headaches. Leaving off a var keyword inside a function can produce surprising results.

var i = 1;
function modifyGlobalByAccident() {
// Re-use of global i but was
// intended to be local to function!
for (i = 0; i < 10; i++) {
// ...
}
}
modifyGlobalByAccident();
alert(i); // Now contains 10, NOT 1!

It can be difficult to track down interactions like this, particularly when there is a lot of code between a variable's initial declaration and its subsequent overwriting. If we intended to modify the global variable, this code is fine (we'll see how to make things more obvious later). However, it does underscore the stance that global variables are evil and can lead to difficult-to-debug code.

The other potential scoping gotcha is that in JavaScript only functions have their own scope. For example, blocks inside a for loop do not have their own scope, and variables declared within such a block will overwrite variables of the same name outside the block.

This is quite different from Java, where anything inside curly brackets ({}) is its own block and enjoys a distinct variable space. In the example that follows, the initial value of aName will be overwritten:

var aName = "Dave";
var names = ["Dave", "Nick", "Omar"];
for (var i = 0; i < names.length; i++) {
var aName = names[i];
}
alert(aName); // Alerts "Omar", NOT "Dave" (sorry, Nick).

Even though we used the var keyword, we are not creating a new variable named aName. It's the same one. This is at least as bad as the default global scope. Perhaps it is worse when coming from a Java background. In Java, we believe we can create unique variables wherever we want. In JavaScript, we can't. If we're not vigilant, this can cause subtle bugs that are hard to track down, particularly if we're not looking for scope-related issues.

Using, the var keyword inside functions does create a new variable.

var aName = "Dave";
var names = ["Dave", "Nick", "Omar"];
function foo() {
for (var i = 0; i < names.length; i++) {
var aName = names[i];
}
alert(aName); // Contains "Omar"; local to function.
}
foo();
alert(aName); // Contains "Dave"--the global value.

In the code above, notice that inside the function foo(), we still have access to the aName variable, even though we're outside the for loop. The for loop does not create its own variable scope. Only the function foo has its own scope. It doesn't matter where in the function we put the var aName.

By the same token, if we create a variable inside a function and attempt to refer to it outside of that function, it will be an error.

function bar() {
var baz = "Hello";
alert(baz); // This works.
}
alert(baz); // This doesn't.

JavaScript data types

JavaScript has only a few data types when compared to Java. One important difference is that variables in JavaScript are not statically typed. This implies that a variable initialized to a string in one place can be assigned a number somewhere else. Doing this is rarely a good idea, and can be a source of frustration.

Numbers

Tired of all the numeric types available in Java? JavaScript has only one&mdash;64-bit floating point. Even numbers that look like integers are actually floating point internally. This isn't always an issue, but it is something to be aware of. This should also be remembered when performing what we think is integer division&mdash;the result may not be an integer.

Floating point numbers may be represented conventionally, such as 3.14159, or by using scientific notation, such as 6.02e23.

JavaScript has several built-in values that may be returned from a numeric calculation, the most important being NaN and Infinity. Interestingly, Infinity can be either positive or negative.

NaN is noncomparable using the normal numeric operators, so a isNaN(obj) function is provided. Similarly, isFinite(obj) will test for positive or negative Infinity (when obj is not NaN).

JavaScript provides a Math object that has more complex mathematical operations available. A complete reference is beyond the scope of this book. The object provides many of the expected operations such as Math.sqrt(), Math.sin(), Math.cos(), and so on.

Decimal, hex, octal

Numbers can be represented in decimal, hex, or octal. Hex numbers are prefixed with a 0x, whereas octal numbers are prefixed with a 0.

Conversions

Numbers can be converted to strings with the String(obj) method. However, numbers created in scientific notation will not necessarily be converted to a string containing scientific notation. For example, String(6.23e5) will return 623000, whereas String(6.23e21) will return 6.23e+21 (at least in Firefox).

Strings

JavaScript strings can be delimited with either single or double quotes. There is no functional difference between these two types of quotes. For example, if our string contains a quote of one kind, we can quote the entire string with the other quote. An alternative is to escape either type of quote with a leading backslash () character.

JavaScript supports many of the typical string escape sequences, such as , , and so on. Unicode characters may be embedded in a string by escaping with a uXXXX, where XXXX is the four hexadecimal digits of the Unicode character.

Strings can be concatenated using the plus operator (+). This is similar to Java in the way memory is allocated and a new string is created. If there are a lot of string concatenations, particularly inside a loop, it can be more efficient to use the join method from the Array object:

aString = ["lots", " of", ..., " strings"].join("");

We shouldn't do this unless we're joining a lot of strings&mdash;creating the array and calling the join() method is more expensive when there are only a few strings.

Length and conversions

String objects have a length property used to determine the length of the string in Unicode characters.

String objects contain several useful methods. Again, a complete reference is beyond the scope of this book, but a few methods are worth noting. In addition to the expected charAt, indexOf, lastIndexOf, substring, toLowerCase, and toUpperCase functions, strings also have several regular expression functions (including split, which can take either a string separator or a regular expression to split on). The match, replace, and search functions all take regular expressions. As we've seen previously, a good command of regular expressions can be extremely valuable&mdash;true in the case of JavaScript as well.

JavaScript has a convenient syntax for regular expressions&mdash;we put the expression inside slashes (//). For example, the following example removes HTML<pre> tags from a string:

s = s.replace(/<pre.*>/i, "").replace(/</pre>/i, "");

Okay, I'll only make it a partial exercise for the reader. First, we're using method chaining, which we see in Java. The "i" makes it a case-insensitive regex. The purpose of the backslash in the second regex is to escape the forward slash. Otherwise, JavaScript would think our regex was done being defined, and would promptly blow up since the rest of the line wouldn't be legal JavaScript.

Conversions to other types

Strings can be converted to numbers using the Number(obj) method. However, Number("010"), despite the leading 0, will return 10 decimal, not 8 octal. So, we've been warned for all those times when we would be dealing with octal numeric data in strings.

We can also use the parseInt(obj) function. It defaults to base 10 numbers. The parseInt function takes an optional radix parameter, parseInt(obj, radix). This function will stop converting as soon as it sees a character in the string that doesn't make sense in the assumed radix. This can lead to an interesting result if the string passed in contains a leading 0. If we call parseInt("08"), we'll actually get the number 0 back. This is because the leading zero makes parseInt believe it's parsing an octal number, and 8 is not a legal octal digit.

The plus (+) operator is also a unary operator that, when applied to a string, will convert it to a number. We'll look at this a bit further when we look at type coercion.

Arrays

JavaScript arrays are objects. In Java, they're sort of objects, but don't share many object semantics, and require java.lang.reflect.Array for direct manipulation. As in Java, they can be created with an immediate syntax, using brackets [] around the array values.

var array1 = [1, 2, 3, 4, 5];

Individual array elements can then be accessed using an array index, which starts at zero. For example, array1[0] will return the number 1.

Unlike Java, JavaScript arrays are actually always associative arrays, more similar to maps than arrays. When we access array1[0], the index is converted to a string.

Because of this we can say things such as array1["foo"] = "bar", which is more like a map than the Java arrays we're familiar with. This implies that arrays can actually be used like a structure (although objects are probably a better choice, as we'll see next).

Arrays have a length property which is used to determine the length of the array. The length property can also be used to append objects to the end of an array as shown here:

var a1 = [1, 2, 3];
a1[a1.length] = 4; // a1 now contains [1, 2, 3, 4].

We can also add elements beyond the end of an array:

a1[8] = 42;

This puts the number 42 at array index 8 (arrays are zero-based, so the 9th position). What about the values at uninitialized indices? They're filled with the undefined value (not null).

Array functions

Arrays have their own handy collection of functions (not covered in detail here). To cover in brief, the following are some methods included in the collection:

  • concat: For concatenating one array to another

  • join: For joining array elements, which takes an optional separator argument (the default is ",") and returns a string

  • pop and push: For using arrays like a stack

  • slice: For getting a portion of an array

  • sort: For sorting arrays, taking an optional function as an argument (we'll look at this later)

  • splice: Exhibits fairly complicated behavior.

Exception handling

JavaScript has exception handling similar to that of Java, but slightly different. One difference is that we can use only a single catch block. We still have finally blocks.

Throwing an exception is slightly different. We have (at least) two options. We can throw a new JavaScript Error object as follows:

function foo() {
throw new Error("Foo threw me");
}

If this exception occurs outside of a try/catch block under Firefox with Firebug, we'll see the string we passed to the Error function in the JavaScript console.

We can actually throw an object of any type, not just JavaScript's Error object. For example, we might just create an anonymous object with the "name" and "message" properties (we'll get to anonymous objects next):

function foo() {
throw { name: "FooException", message: "Foo threw me." };
}

If we call foo() outside of a try/catch block, the behavior is browser specific. For example, under Firefox with Firebug, we'll see a message in our console similar to this:

uncaught exception: [object Object]

This is not particularly useful. We can give our anonymous object a toString() method, but that starts to make our throw statement bulky. A more convenient way is to create a function that returns the exception object, complete with a toString() function. We'll see many ways to go about this in the next section. We create our own exception objects for the same reason we do it in Java&mdash;the ability to add exception-specific data that can be acted upon, displayed, and so on.

Introduction to JavaScript objects and OOP

Objects are at the heart of JavaScript, although functions (which are objects themselves) play a surprisingly significant role once we get more advanced. While there are a few ways to create a new object in JavaScript, the canonical method, particularly for structure-like objects, is to use the {} notation, which creates an anonymous object.

var o = {};

JavaScript objects can be thought of as a unification of objects and hash tables. An object is simply an unordered collection of name-value pairs. A name can be any string. Similarly, a value can be any value&mdash;including numbers, strings, arrays, other objects, and even functions. In a sense, every JavaScript object is a tiny database.

This is underscored by the JavaScript anonymous object syntax. By supplying a comma-separated list of name-value pairs, we can create our own data structures, which can then be accessed in an intuitive way:

var aDude = {
fname: "Dave",
lname: "Newton",
age: Infinity // Notice no trailing comma!
};
aDude.fname; // Returns "Dave".
aDude["fname"]; // *Also* returns "Dave".

When we create a name-value pair, we do not need to quote the "name" string, unless it is a JavaScript reserved word. Note that there are many JavaScript reserved words that aren't actually used in the language (this is historical and somewhat confusing). Also notice that the last name-value pair does not have a trailing comma. This is significant, as not all browsers will allow a trailing comma.

Recall that we said an object can hold any value type; this includes other objects too.

var aDude =
fname: "Dave",
lname: "Newton",
age: Infinity,
address: {
street: "271828 E St",
state: "Confused",
zip: "69042"
}
};

We can access members of aDude's address object as expected&mdash; aDude.address.state. This usage of anonymous object is known as JSON (JavaScript Object Notation).

Open objects and object augmentation

Values can be added to objects at any time. For example, we could add an arbitrary property to the aDude object after it's created by using either dot notation or array notation.

aDude.newProperty = "I'm a new property.";
aDude["anotherNewProperty"] = "Another new property.";

Properties may be accessed using either dot or array notation.

Object values can be functions

We'll explore this further as we go along. However, note that we can use a function as a value in an object. In other words, functions can be the value of a variable. Functions are first class objects in JavaScript.

aDude.toString = function() {
return "aDude [fname=" + this.fname + ", lname="
+ this.lname + "]"; }
alert(aDude); // Alerts "aDude [fname=Dave, lname=Newton]".

Notice the this keyword used in the snippet. It is similar to Java's this, but significantly different depending on the situation in which it is used (we'll talk about this a bit later). A complete treatise of JavaScript OOP is well beyond the scope of this book, but we'll cover some basics.

Object maker functions

Objects can be created by dedicated creation functions. This methodology can be used to make sure objects are always created in a specific way, are assigned default values (if no specific value is specified), and so on. We'll learn more about JavaScript constructors a bit later on (they're also just functions, although not the specialized ones within a class as in Java).

function makeAdude(fname, lname) {
return { fname: fname, lname: lname };
}
var aNewDude = makeAdude("Omar", "Velous");
aNewDude.fname; // Returns "Omar".

Here, we're simply returning an anonymous object from our function. However, the function ensures that at least the object's fname and lname values are initialized.

Functions

We've already seen that functions are first class objects. We've also seen one way in which this can be exploitedthe toString() function in our object above was a function value. We'll cover some interesting aspects of JavaScript functions, some of which may not seem useful until we begin discussing modules and encapsulation.

When we declare a normal function in JavaScript, we're actually creating a variable that holds that function as its value&mdash;that's what the function keyword does. This is shorthand for the function operator, which takes an optional name, a parameter list, and a block of statements. The two examples given here are functionally equivalent:

function f1() {
alert("Hello from f1");
}
var f2 = function () {
alert("Hello from f2");
};
f1();
f2();

Function parameters

Function parameters, like all JavaScript variables, are untyped. In addition, we can call a function with fewer (or more) parameters than listed in the function definition.

function f1(param1, param2) {
...
}
f1(); // That's fine: param1 and param2 are undefined.
f1("foo", "bar", "baz", "plugh"); // That's fine too...

This alone can make JavaScript a bit tricky at times, but that's the nature of the beast. We can always check for a specific number of parameters. We can even check for their types to a degree. However, this is surprisingly uncommon (at least to Java programmers).

Parameters that don't receive a value from the function call are undefined, not null. Recall that using == (equal) will not show the difference between undefined and null, whereas using === (strict equal) will show the difference. For example, if we called the function with a single parameter:

f1("hello");

Inside f1(), the parameter param1 would be filled with"hello". Param2, on the other hand, would be undefined (not null). If null was a legitimate value for param2, we might need to distinguish between null (implying the parameter was potentially passed) and undefined (implying that the parameter wasn't passed in).

Using the || operator allows us to supply default parameter values if we don't receive one in the call. For example, let's assume the param2 parameter is optional. When it's not provided, we'll supply a default value of 42 as shown here:

function f1(param1, param2) {
param2 = param2 || 42;
...
}

Accessing arguments beyond those listed in the function definition resembles array access, but only in appearance. Functions receive a pseudo-parameter named arguments. It acts like a JavaScript array in a way that we can access its length property and elements using array notation's [] (square brackets). However, the arguments value does not inherit Array's methods like join, sort, and so on (it's not an Array, but is dressed as one).

function f1(param1) {
var anArg = arguments[1] || "Default value";
...
}

Some trickery

To make matters even a bit more confusing, we can actually apply Array's methods on the arguments parameter using Array's prototype (we'll cover prototypes in a little while).

function f1() {
var csvArgs = Array.prototype.join.apply(arguments);
alert(csvArgs);
}

While this is actually useful sometimes, it can cause headaches for those unclear about how JavaScript works. It can also cause headaches for those who do know JavaScript.

One way of testing to see if our function has received the proper number of arguments is to check against a value inside the method, such as:

function f1(param1, param2) {
if (arguments.length != 2) {
alert("Error!");
return;
}
...
}

This works, but requires us to remember updating the length to check against. For example, if we changed our method to require three arguments or a single argument, we'd have to remember to update our argument length check. We can automate this length checking by using the length property of the function objects (it used to be called arity).

function f1(param1, param2) {
if (arguments.length != f1.length) {
alert("Error!");
return;
}
...
}

The length property refers to how many arguments the function was created with&mdash;two in this case. Note how we refer to the function's name inside the function we're defining. The function f1 can be referred to as f1 inside itself (remember what we said about headaches?) We could also throw an exception rather than show an alert. Which is more appropriate depends on the application and our JavaScript architecture.

Inner functions

As a variable can hold a function, we can define a function at any place where a value is expected, including inside other functions.

function f1() {
function f2() {
alert("Meh.");
}
f2();
}
f1();
f2(); // Will this work? Nope.

Function f2() is not available once we've exited the f1() function, for two reasons:

  • The equivalence of the two ways of declaring functions (including the implicit var)

  • The scoping rules of functions we discussed earlier, which state that variables declared within a function with the var keyword are available only in that function

This is one way to hide functionality to avoid conflicts with our own or third-party JavaScript libraries. We'll also learn some other ways. However, this is still an important aspect of functions that comes in to play occasionally, and can always be used when a function needs to be used only locally, or we want to restrict its usage.

Closures

Closures are one of the terms often used by programming language junkies. They're an extremely powerful concept, occasionally difficult to grasp, and capable of both wondrous and terrifying behavior.

To put it briefly, closures are functions evaluated in a context containing other bound variables. In some ways, it sounds more complicated than it actually is.

What this actually means is that an inner function (as described in the previous section) has access to the variables from the outer function. As a simplistic example, consider the following:

function f1() {
var s = "Hello";
function f2() {
alert(s);
}
f2();
}
f1();

Although this is not particularly useful, it illustrates the point. How can we make this useful? One key consideration is that the inner function will maintain its reference to the outer variable's value at the time of the outer function's invocation. Here's a canonical example of closures, which highlights this feature:

function makeAdder(x) {
return function(n) {
return n + x;
};
}

The makeAdder() function relies on two things:

  • We can use a function as a value. Here, the makeAdder() function returns a new function.

  • The value of the argument x given to makeAdder()is saved across invocations. In other words, each time we call makeAdder(), the function returned maintains the value of x&mdash;the value we passed to makeAdder().

We can then call the function returned by makeAdder(). It will add the value of x, passed to makeAdder(), to the argument we pass to the function returned by makeAdder(). Remember what I said about headaches? This is much easier to demonstrate than to explain:

var add10 = makeAdder(10);
add10(1); // Returns 11.
add10(32); // Returns 42.

The code above creates a function that will add the value passed to makeAdder(), to any value we pass to the function created, by calling makeAdder(). As the value x passed to makeAdder()is unique to each invocation, we can easily make functions that add different values to their arguments.

var add10 = makeAdder(10);
var add30 = makeAdder(30);
add10(32); // Returns 42.
add30(12); // Also returns 42.

Again, the makeAdder() function itself isn't particularly useful, but the idea of closures is an important concept that can be used to neatly encapsulate functionality and reduce code size in many situations (we'll see closures again in a little bit).

Introduction to JavaScript classes

JavaScript is an object oriented language, but not in the same way that Java is. The bottom line is that JavaScript doesn't have classes. JavaScript has a new keyword, which actually adds to the confusion.

OOP without classes can be disturbing to those of us familiar only with more typical OO languages such as Java or C++. JavaScript uses prototypal inheritance, where inheritance is achieved by cloning existing objects and adding new (or modifying existing) functionality. So how does the new keyword fit in?

Creating classes

We can model classical inheritance in JavaScript using functions and the new keyword. It will look different from what we're used to, and it may not be the best way to program JavaScript. However, it's used to a considerable extent, and hence it's important to understand the mechanisms.

Instead of creating a class, we'll create a function. However, to differentiate it from a normal function, we'll call it a constructor function and name it starting with an upper-case letter. This is a convention, not a rule. However, for Java programmers, it helps ease the transition into what is ultimately a very different programming paradigm.

Like Java, JavaScript has a this keyword. However, it is used differently depending upon context, and there are some restrictions regarding when it can be used (and how it will work). We'll get to one of the bigger issues with the this keyword in a bit. For now, we'll assume we can use it in a somewhat similar way to how we use it in Java.

function OurFirstClass(aParam) {
this.aParam = aParam;
}

We can use this in our JavaScript code by applying the new operator to the OurNewClass function.

var inst1 = new OurFirstClass("Hello");
inst1.aParam; // "Hello"

Variable and function access

In Java, our classes have both data and methods. We can do something similar in JavaScript, although it looks fairly unfamiliar in its raw form. What makes it more confusing is the fact there are two ways to accomplish similar looking, but different things. Also, there are scoping rules which will add to the confusion. JavaScript has an interesting access model.

To try and pace out the confusion, we'll examine several ways of defining variables and functions in our pseudo-classes. First, we'll look at three ways of defining functions in our pseudo-classes. (Why pseudo-classes? JavaScript doesn't have classes! These are functions.) We'll try to relate JavaScript concepts to Java, wherever applicable; JavaScript purists will take (justifiable) umbrage.

function Pseudo1() {
function aPrivateFunc() {
...
}
this.aPrivilegedFunc = function () {
...
}
}
Pseudo1.prototype.aPublicFunc = function () {
...
}

The aPrivateFunc() function is as its name implies&mdash;private. It cannot be called outside of the object. It's not even available from the pseudo-class's public functions.

The aPrivilegedFunc() function can access private variables and functions, and is available to both public and private functions. Also note that privileged functions can be deleted or replaced from the public side, which may or may not be something we want to allow.

The aPublicFunc() function, defined outside our pseudo-class function, is a true public function. Adding a function to a pseudo-classes prototype is the normal way of adding public methods. Functions added to a prototype are available to objects that inherit from the prototype (which we'll examine later).

We create an instance of our pseudo-class the same way we've seen previously.

var inst1 = new Pseudo1();

We can call both the prototype function aPublicFunc(), and the privileged function aPrivilegedFunc() at our top level.

inst1.aPublicFunc();
inst1.aPrivilegedFunc();

What about accessing any of these functions from the remaining functions? (Remember what we said about headaches? Hang on.)

We cannot call the private function aPrivateFunc() from a reference to inst1.

The prototypal function aPublicFunc() can access aPrivilegedFunc(), but must use the this prefix.

Pseudo1.prototype.aPublicFunc = function () {
alert("aPublicFunc");
this.aPrivilegedFunc();
}

The privileged function aPrivilegedFunc() may call aPrivateFunc(), but must not use the this prefix. Also, it may call aProtoFunc(), but must use the this prefix. (The aProtoFunc() function is coming up in the next section&mdash;be patient!)

this.privilegedFunc = function () {
alert("privilegedFunc");
privateFunc();
this.protoFunc();
}

Privileged functions may call other privileged functions, but must use the this prefix.

JavaScript's "this" keyword

Private functions may call neither prototypal nor privileged functions, unless we play a trick on JavaScript (which we do often). Because of the way the this operator was defined, it's a bit wrong when it comes to private functions. However, if we create a private variable (conventionally called that), we can access the instance from private methods.

function Pseudo1() {
var that = this;
function aPrivateFunc() {
alert("aPrivateFunc");
that.anotherPrivilegedFunc();
that.aProtoFunc();
}
this.anotherPrivilegedFunc = function () {
alert("priv2");
}
this.aPrivilegedFunc = function () {
alert("aPrivilegedFunc");
aPrivateFunc();
}
}
Pseudo1.prototype.aProtoFunc = function () {
alert("aProtoFunc");
}
var inst1 = new Pseudo1();
inst1.aPrivilegedFunc();

This and that (oh, another this pun&mdash;we've got a million of them), brings up the topic of class variables, but they're simpler than functions. They're not commonly added to the prototype. Although there's no reason they can't be, it would be like a static variable in Java, and mutable.

Private variables, such as that (covered in the above code), are accessible by private and privileged functions, but not prototypal (public) functions. Privileged variables (this.xxx variables) are accessible by public (prototypal) and privileged functions by using the this keyword, and by private functions by using the that trick, as demonstrated above.

The last thing to consider (for now) is that public members can be created at any time by adding them to the pseudo-class's prototype. Neither private nor privileged members can be added outside the constructor function.

Prototypes

We've seen some hints about the prototypal inheritance of JavaScript above, when we created a function on a class's prototype:

Pseudo1.prototype.aProtoFunc = function () {
alert("aProtoFunc");
}

This is another way to add functions to a class. Each instance of Pseudo1 will have a aProtoFunc function. How is this used for inheritance? Let's say we want to subclass Pseudo1, a subclass named (uncreatively) Pseudo2:

function Pseudo2() {};
Pseudo2.prototype = new Pseudo1();

If we create an instance of Pseudo2, we can call aProtoFunc() on it:

var pseudo2 = new Pseudo2();
pseudo2.aProtoFunc(); // This works.

Pseudo2 may contain its own methods, defined using either its prototype or in the Pseudo2 function body (using the this keyword, as seen previously). Creating subclasses this way is a bit unwieldy. There are several ways to make this more concise. The canonical method uses a function to encapsulate creating the subclass function, setting the prototype, and returning an instance (the following is Douglas Crockford's version):

function object(o) {
function F() {}
F.prototype = o;
return new F();
}

JavaScript modules and OOP

What's probably more important is how we can write JavaScript that's unlikely to conflict with other JavaScript libraries, methods, and data, (including our own) and we don't even need the package keyword.

Creating a namespace

The easiest way to isolate our JavaScript code is to just put it inside an anonymous object. This is the tactic taken by many major JavaScript libraries, and is wonderfully simple. For simply isolating functions and variables, it's a reasonable technique. It's also safer than putting all of our functions and variables into the global namespace.

As a simple example, we might create an object named MOD1 for the JavaScript specific to a given page. It doesn't matter what we call it, but it should be unique on the page.

var MOD1 = {
myAlert: function (s) {
alert(s);
}
}

We'd then call the myAlert() function by prefacing it with the module name.

MOD1.myAlert("Hi!");

This way, if any other JavaScript loaded after ours has a function named myAlert(), our page's myAlert() won't be replaced by the newer definition. It's hidden inside the MOD1 object. We only need to refer to it through our module.

In the next chapter, we'll return to this methodology and see how it can be broken down depending on our requirements, and learn some additional modularization tricks.

Summary

JavaScript. Whew. Gotta love it (or not). The previous sections will, however, help us understand some of what we'll see in the wild, and start us on the path to actually enjoying JavaScript. It is, as Dr. Sobel says, a process.

This chapter looks at some of the syntactical quirks of JavaScript, particularly those that can trip up Java programmers. The chapter also gives an overview of JavaScript's version of object-oriented programming, and there's more to come.

What's next? More JavaScript! We're now going to start utilizing it in our application. In order to do that we'll start diving in to the DOM, the internal representation of our webpages, and Cascading Style Sheets (CSS), used both for styling our webpages and for identifying DOM elements of interest.

References

A reader can refer to the following:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.101.109