"Atwood’s Law: any application that can be written in JavaScript, will eventually be written in JavaScript.
This Appendix is dedicated to the JavaScript programming language. While all chapters of this book show how JavaScript frameworks can greatly minimize the amount of the JavaScript code that you need to write manually, you still need to understand the language itself. We assume that you have some programming experience with any programming, understand the HTML syntax, and are familiar with general principal of communications between Web browsers and Web servers. We’ve included the word advanced in the title of this Appendix because of these assumptions. We’ll start with covering basics of the language, but then quickly progress to such advanced topics as prototypal inheritance, callbacks, and closures.
Note
|
If you’re absolute beginner with Web development and had no previous exposure to JavaScript, consider reading one of the fundamental tutorials covering each and every detail of JavaScript. We can recommend you the book "JavaScript: The Definite Guide", Sixth Edition by David Flanagan. |
Besides the JavaScript coverage this Appendix includes a section on the tools (IDEs, debuggers, Web inspectors et al.) that will make your development process more productive.
The JavaScript programming language was designed in 1995 by Brendan Eich, who was working for Netscape Communications Corporation at the time. His goal was to allow developers create more interactive Web pages. Initially the name of this language was Mocha, then LiveScript, and finally Netscape agreed with Sun Microsystems, creator of Java to rename it to JavaScript.
A year later, the language was given to an international standards body called ECMA, which formalized the language into ECMAScript standard so that other Web browser vendors could create their implementation of this standard. JavaScript is not the only language created based on the ECMAScript specification - ActionScript is a good example of another popular dialect of ECMAScript.
To learn more about the history of JavaScript from the source watch the Brendan Eich’s presentation "JavaScript at 17" at O’Reilly’s conference Fluent 2012.
Vast majority of today’s JavaScript code is being executed by the Web browsers, but there are JavaScript engines that can execute JavaScript code independently. For example, Google’s V8 JavaScript engine implements ECMAScript 5 and is used not only in the Chrome browser, but can run in a standalone mode and can be embedded in any C++ application. Using the same programming language on the client and the server is the main selling point of node.js, which runs on top of V8. Oracle’s Java Development Kit (JDK) 8 will include the JavaScript engine Nashorn that not only can run on both the server and the client computers, but also allows to embed the fragments of JavaScript into Java programs.
In the ninetieth, JavaScript was considered a second class language used mainly for prettifying Web pages. In 2005 the techniques known as AJAX (see Chapter 2) made a significant impact to the way Web pages were built. With AJAX, the specific content inside the Web page could be updated without the need to make a full page refresh. For example, Google’s gmail that inserts just one line at the top of your input box whenever the new email arrives - it doesn’t usually re-retrieve the entire content of your Inbox from the server and definitely doesn’t re-render the Web page.
AJAX gave a second birth to JavaScript. But the vendors of Web browsers were not eager to implement the latest specifications of ECMAScript. Browsers’ incompatibility and lack of good development tools prevented JavaScript from becoming the language of choice for Web applications. Let’s not forget about the ubiquitous Flash Player – a popular VM supported by all desktop Web browsers. Rich Internet Applications written in ActionScript were compiled into the byte code and executed by Flash Player on the user’s machine inside the Web browser.
If AJAX saved JavaScript, then rapid proliferation of tablets and smartphones made it really hot. Today’s mobile devices come equipped with modern Web browsers, and in the mobile world there is no need to make sure that your Web application will work in the 4-year old Internet Explorer 8. Adobe’s decision to stop supporting Flash Player in the mobile Web browsers is yet another reason to turn to JavaScript if your Web application has to be accessed from smartphones or tablets.
ECMASript, 5th Edition has been published in 2009 and is currently supported by all modern Web browsers. If you are interested in discovering if specific features of ECMAScript 5 are supported by a particular Web browser, check the latest version of the ECMAScript 5 compatibility table. At the time of this writing the snapshot of the Chrome Browser v. 22 looks as in ECMAScript 5 Compatibility Sample Chart below:
JavaScript became the lowest common denominator available on thousands of different devices. Yes, the JavaScript engines are not exactly the same on thousands devices that people use to login to Facebook, but they are pretty close, and using some of the JavaScript frameworks spare you from worrying about their incompatibilities.
JavaScript is an interpreted language that arrives to the Web browser as text. The JavaScript engine optimizes and compiles the code before the execution. The JavaScript engine is a part of the Web browser, which will load and execute the JavaScript code embedded or referenced between the HTML tags <script>
and </script>
. JavaScript was originally created for Web browsers, which were supposed to display whatever content has successfully arrived. What if an image has not arrived from the server? You’ll see a broken image icon. What if erroneous JavaScript code with syntax errors has arrived to the browser? Well, the engine will try to execute whatever code has arrived. The end users may appreciate such browser’s forgiveness when at least some content is available, but the software developers should be ready to spend more time debugging (in multiple browsers) the errors that could have been caught by compilers in other programming languages.
JavaScript is a weakly typed language hence the developers don’t have a luxury of strong compiler’s help that Java or C# developers enjoy. For those unfamiliar with weakly typed languages, let us explain. Imagine that if in Java or C# instead of declaring variables of specific data types everything would be of type Object
, and you could assign to it any value – a string, a number, or a custom object Person
. This would substantially complicate the ability of the compiler to weed out all possible errors. You don’t need to declare variables in JavaScript – just assign a value and the JavaScript engine will figure out the proper type during the execution of your code.For example, the variable named girlfriend
will have a data type of String
:
girlfriendName=“Mary”;
Since we haven’t used the keyword var
in front of girlfriend
, this variable will have the global scope, which is a big no-no. Creating your global variables in an Web application that often includes multiple libraries from different vendors can easily create situations when the variable’s value has been accidentally replaced by someone else’s code. Besides, if the application uses concurrent execution (read about Web Workers in Appendix B) using global variable can lead to race conditions. Pretty soon you’ll see how to limit the scope of application’s variables to avoid polluting global space.
Variables declared with the var
keyword inside functions are have local scope and are not visible from outside of the function. Consider the following function declaration:
function addPersonalInfo(){
var address ="123 Main Street"; // local String variable
age=25; // global Number variable
var isMarried == true; // local boolean variable
isMarried == "don't remember"; // now it's of String type
}
The variables address
and isMarried
are visible only inside the function addPersonalInfo()
. The variable age
becomes global because of omission of the keyword var
.
The variable isMarried
changes its type from Boolean
to String
during the execution of the above script, and JavaScript engine won’t complain assuming that the programmer knows what she’s doing. So be ready for the run-time surprises and allocate a lot more time for testing than with programs written in compiled languages.
Download and install Aptana Studio 3 from http://aptana.com. Start Aptana IDE, then close its initial tab view by clicking on the little X on the tab. Then customize the color theme of this IDE by clicking the rainbow-colored circle on its toolbar. We usually select the theme called Eclipse. After the first start of Aptana you’ll see the message on the left side that reads ``There are no projects in your workspace. To get started, please create or import an existing one.''
If you want to start playing with the code samples that come with this book, click on the button Import Project, select the General | Archive file. Find the zip file you’d like to use, e.g. chapter2.zip, and press Finish. The project from the selected zip file will be imported into the Aptana’s workspace, which is nothing more than a folder on the disk where the source code will reside. When you work in Aptana IDE you see a set of views (panels). This set is called perspective. For Web projects Aptana uses Web perspective, which is indicated at the top right corner. Pressing the icon with a little pus sign at the top right allows to open another perspective with its own set of views.
Let’s get started with creating a project from scratch by pressing the button Create Project on the left. You could have also created a new Web Project using the File menu. On the next window you’ll need to select a wizard, and we’ll be always working with Web Projects throughout this book. The next window will offer you to select a project template - let’s stick to the simplest one - Default Project. Name it MyFirstProject.
To add an HTML file to this project select the project (its name becomes highlighted) and then select the menu File | New From Template | HTML | HTML5 Template. Aptana will offer you new_file.html the name of this file - no need to change it for now. Just press finish and you’ll see a window similar to the one shown on Aptana IDE with one HTML5 file.
Right-click on the new_file.html and select the menu Run as JavaScript Web project. Don’t get upset that there is no JavaScript code there yet - we’ll add it pretty soon. Aptana starts its built-in Web server that by default runs on the port 8020 (it’s configurable in Aptana Preferences). The Web browser opens up and displays the page that looks like the one in Running MyFirstProject. Aptana has used its default template to generate HTML file. The template can be changed to your liking, and you can read about it in Aptana’s documentation at http://bitly.com/LRqRdU.
Tip
|
If you have your index.html open in Aptana’s editor, you can simply press the green triangle on the toolbar and run this file in the Web browser. |
To configure the Web Browser that Aptana should open by default, open its Preferences window and select the Web browser of your choice under the General section. The authors of this book use mainly Google Chrome browser, but in this book we’ll be giving using Firefox as well just to show you that developers have a choice too. Many examples in this chapter use the Firefox with installed add-on Firebug, so start with making Firefox your default browser in Aptana.
Tip
|
you can find various HTML5 boilerplate projects on the Web that can be used as a starting point of your project. You don’t have to select the Aptana’s HTML5 boilerplate if it doesn’t fit your needs. For example, you can download a bare minimum boilerplate Shibui or more comprehensive HTML5 Boilerplate. Just download and unzip such a boilerplate project into your Aptana’s workspace and start adding your code, styles, and other resources. |
Software developers either directly include the JavaScript code to the HTML document by placing it between the tags <script>
and </script>
or include a reference to the external location of the code (e.g. a local file name or a URL) in the src
attribute of the <script>
tag. We usually place the <script>`tags at the end of HTML file. The reason is simple - your JavaScript code may be manipulating with HTML elements, and you want them to exist by the time the script runs. The other way to ensure that the code will run only after the Web page has loaded is by catching window’s `load
event (you’ll see such example later in this chapter in the section on browser’s events). Some JavaScript frameworks may have their own approach to dealing with HTML content and in Chapter 4 you’ll see that the main HTML file of the Web application written with Ext JS framework has <script>
tags followed by the empty <body>
tags. But let’s keep things simple for now.
Add the following fragment at the very end (right above the closing </body>
tag) of the new_file.html from Aptana IDE with one HTML5 file.
<script>
alert("Hello from JavaScript");
</script>
Run the new_file.html in Aptana and you’ll see the following output in your Web browser:
Note that the Alert
popup box is shown on top of the Web page that already rendered all of its HTML components. Now move the above code up to the end of the <head>
section and re-run new_file.html. The picture is different now - the Alert box is shown before the HTML rendering is complete.
In this simple example this doesn’t cause any malfunctioning of the code, but if our JavaScript would need to manipulate with HTML elements, we’d run into issues of accessing non-existent components. Beside simple Alert
box, JavaScript has Confirm
and Prompt
boxes, which allow asking OK/Cancel type of questions or request some input from the user.
Tip
|
In a real life you won’t be deploying your projects under Aptana’s internal Web server. When you code is tested you can FTP it to a remote server of your choice, e.g. Apache Web Server or IIS. Right-click on your Aptana project and select the menu option Publish. This will allow you to configure the FTP connection to your remote server and publish your working code there as you wish. |
The best way to learn any program is to run it step by step through a debugger. While some people appreciate using debuggers offered by the IDE, we prefer to debug using great tools offered by the major Web browsers:
-
Firefox: Firebug add-on
-
Chrome: Developer Tools
-
Internet Explorer: F12 Developer Tools
-
Safari: the menu Develop
-
Opera: Dragonfly
We’ll be doing most of the debugging either in Firebug or Chrome Developer Tools. Both of them provide valuable information about your code and are easy to use. To get Firebug go to www.getfirebug.com and press the red button Install Firebug and follow the instructions. In Firefox, open the Firebug panel from the menu View.
Select the Console option on the Firebug toolbar, enable the console and enter alert("Hello from JavaScript")
after the >>> sign and you’ll see the Alert box. To enter multi-line JavaScript code press the little circle with a caret at the bottom right corner and Firebug will open a panel on the right, where you can enter and run your JavaScript code.
This was probably the last example where we used the Alert()
popup box for debugging purposes. All JavaScript debuggers support the console.log()
for printing debug information. Consider the following example that illustrate strict equality operator ==. Yes, it’s three equal signs in a row. This operator evaluates to true if the values are equal and the data types are the same.
var age=25;
var ageStr="25";
if (age==ageStr){
console.log("The values of age and ageStr are equal");
}
if (age==ageStr){
console.log("The values of age and ageStr are strictly equal");
} else{
console.log ("The values of age and ageStr are not strictly equal");
}
Running this code in the Firebug console produces the following output:
Tip
|
You can also use console.info() , console.debug() , and console.error() so the debuggers may highlight the output with different colors or mark with different icons.
|
Tip
|
For more information about debugging JavaScript refer to the code samples illustrated in Firebug’s Script panel and Firebug’s Script panel at a breakpoint. |
JavaScript can be called an object-oriented language cause it allows an object to inherit existing functionality from another object, and you can encapsulate the data and restrict the data access. It’s done not as simple as in classical object-oriented languages, but it’s possible. Now comes the chicken or the egg dilemma. What should be explained first - the syntax of functions or creation of objects? Understanding of objects is needed for some of the function code samples and visa versa. We’ll start with simple function use cases, but will be switching to objects as needed.
Many of the readers can have experience with object-oriented languages like Java or C#, where classes can include methods implementing required functionality. Then these methods can be invoked with or without instantiation of the objects. If a JavaScript object includes functions they are called methods. But JavaScript functions don’t have to belong to an object. You can just declare a function and invoke it. Just like this:
//Function declaration
function calcTax (income, dependents){
var tax;
// Do stuff here
return tax;
}
//Function invocation
calcTax(50000, 2);
var myTax == calcTax(50000,2);
Warning
|
Please note that the data types of the function parameters income and dependents are not specified. We can only guess that they are numbers based on their names. If a software developer won’t bother giving meaningful names to function parameters, the code becomes difficult to read.
|
After the function calcTax()
is invoked and complete, the variable myTax
will have the value returned by the function.
Another important thing to notice is that our function has a name calcTax. But this is not always the case - JavaScript allows functions to be anonymous - you’ll see an example of anonymous functions in the function expressions below (note the absence of a name after the keyword function
).
Note
|
If you see the line of code where the keyword function is preceded by any other character this is not a function declaration, but a function expression.
|
Consider the following variation of the tax calculation sample:
//Function expression
var doTax=function (income, dependents){
//do stuff here
return tax;
}
//Function invocation
var myTax=doTax(50000,2);
In the code above the function
keyword is being used in the expression - we assign the anonymous function to the variable doTax
. After this assignment just the text of the function is assigned to the variable doTax
- the anonymous function is not being invoked just yet. It’s important to understand that even though the code of this anonymous function ends with return tax;
actually, the tax calculation and return of its value is not happening until the doTax()
is invoked. Only then the function is evaluated and the variable myTax
will get whatever value this function returns.
Yet another example of a function expression is when it’s placed inside the grouping operator - parentheses as shown below. As in an arithmetic expressions, this means that the the content inside the expressions has to be evaluated first, and then used in an expression:
(function calcTax (income, dependents){
// Do stuff here
});
The outermost parentheses hide its internal code from the outside world creating a scope or a closed ecosystem, where the function’s code will operate. Try to add a line invoking this function after the last line in the above code sample, e.g. calcTax(50000,2)
, and you’ll get an error - "calcTax is not defined". There is a way to expose some of the internal content of such a closure and you’ll see how to do it later in this appendix.
If you’ll take away the outermost parentheses and the closing semicolon, you’ll get the function declaration, which will be subject to hoisting (we’ll explain it explained soon). Function expressions are usually a part of a larger expression. For example, if you’ll add a couple of parentheses at the end of this expression, you’ll get a self-invoked function. This extra pair of parentheses will cause the function expression located in the first set of parentheses to be executed right away.
(function calcTax (income, dependents){
// Do stuff here
})();
Tip
|
The topic "Function declaration vs. function expressions" is one of those fuzzy JavaScript areas that can cause unexpected behavior of your code. Angus Croll published a well-written article on this subject. |
JavaScript objects are simply unordered collections of properties. You can assign new or delete existing properties from the objects during the runtime whenever you please. In classical object oriented languages there are classes and there are objects. However JavaScript doesn’t have classes.
Note
|
The ECMAScript 6 specification will include classes too, but since it’s a work in progress we won’t consider them as something useful in the today’s world. If you’d like to experiment with the upcoming features of JavaScript, download the Chrome Canary browser, go to chrome:flags and enable experimental JavaScript. Chrome Canary should be installed on the computer of any HTML5 developers - you can use today those features that will be officially released in Chrome Developer Tools in about three months.
|
In JavaScript you can create objects using one of the following methods:
-
Using object literals
-
Using new Object() notation
-
Using Object.create()
-
Using constructor functions and a new operator.
Technically, there can be other APIs that implicitly create objects, e.g. JSON.parse(), but let’s keep things simple.
Note
|
In JavaScript everything is an Object. Think of Object as of a root of of the hierarchy of all objects used in your program. All your custom objects are descendants from Object. |
The easiest way to create a JavaScript object is by using the object literal notation. The code sample below starts with a creation of an empty object.
var t == {} // create an instance of an empty object
The following line of code creates an object with one property salary
and assigns the value of 50000 to it.
var a == {salary: 50000}; // an instance with one property
Below, the instance of one more object is created and the variable person
points at it.
// Store the data about Julia Roberts
var person == { lastName: ”Roberts”,
firstName: ”Julia”,
age: 42
};
This object has three properties: lastName
, firstName
, and age
. Note that in object literal notation the values of these properties are specify using colon. You can access the properties of this person using the dot notation, e.g. person.LastName
. But JavaScript allows yet another way of accessing the object properties by using square bracket syntax, for example person["lastName"]
. In the next code sample you’ll see that using the square brackets is the only way to access the property.
var person == {
"last name": "Roberts",
firstName: "Julia",
age: 42};
var herName=person.lastName; // (1)
console.error("Hello " + herName); // (2)
herName=person["last name"]; // (3)
person.salutation="Mrs. ";
console.log("Hello "+ person.salutation + person["last name"]); // (4)
-
The object person doesn’t have a property lastName, but no error is thrown
-
This will print "Hello undefined"
-
Using and alternative way of referring to an object property
-
This will print "Hello Mrs. Roberts"
Tip
|
It’s a good idea to keep handy a style guide of any programming language, and we know two of such documents for JavaScript. Google has published their version of JavaScript Style Guide at http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml. A more detailed Airbnb JavaScript Style Guide is available as a github project at https://github.com/airbnb/javascript. And the github version of the JavaScript style guide is located at https://github.com/styleguide/javascript. |
Objects can contain other objects. If a property of an object literal is also an object, you just need to specify the value of this property in an extra pair of curly braces. For example, you can represent the telephone of a person as an object having two properties: the type and the number. The following code snippet adds a nested object to store a work phone as a nested object inside the person’s object. Run this code in the Firebug’s console and it’ll print "Call Julia at work 212-555-1212".
var p == { lastName: "Roberts",
firstName: "Julia",
age: 42,
phone:{
type: "work",
numb: "212-555-1212"
}
};
console.log("Call " + p.firstName + " at " + p.phone.type + " " + p.phone.numb );
What if a person has more then one phone? We can change the name of the property phone
to phones
and instead store an array of objects. JavaScript arrays are surrounded by square brackets, and they are zero based. The following code snippet will print "Call Julia at home 718-211-8987".
var p == { lastName: "Roberts",
firstName: "Julia",
age: 42,
phones:[{
type: "work",
numb: "212-555-1212"
},
{
type: "home",
numb: "799-211-8987"
}]
};
console.log("Call " + p.firstName + " at " + p.phones[1].type + " " + p.phones[1].numb );
Functions defined inside objects are called methods. Defining methods in object literals is similar to defining properties - provide a method name followed by a colon and the function declaration. The code snippet below declares a method makeAppoyntment()
to our object literal. Finally, the line p.makeAppointment();
invokes this new method, which will print the message on the console that Steven wants to see Julia and will call at so-and-so number.
var p == { lastName: "Roberts",
firstName: "Julia",
age: 42,
phones:[{
type: "work",
numb: "212-555-1212"
},
{
type: "home",
numb: "718-211-8987"
}],
makeAppointment: function(){
console.log("Steven wants to see " + this.firstName +
". He'll call at " + this.phones[0].numb);
}
};
p.makeAppointment();
Note
|
Since we already started using arrays, it’s worth mentioning that arrays can store any objects. You don’t have to declare the size of the array upfront and can create new arrays as easy as var myArray=[] or var myArray=new Array() . You can even store function declarations as regular strings, but they will be evaluated on the array initialization. For example, during the greetArray initialization the user will see a prompt asking to enter her name, and, when it’s done, the greetArray will store two strings. The output of the code fragment below can look like "Hello, Mary".
|
var greetArray=[
"Hello",
prompt("Enter your name", ”Type your name here")
];
console.log(greetArray.join(","));
We’ve briefly covered object literals, and you to start using them. In Chapter 2 you’ll be learning about JSON - a popular data format used as replacement for XML in the JavaScript world. Then you’ll see how similar are the syntax of JSON and JavaScript object literals. Now we’ll spend a little bit of time delving into JavaScript functions, and then - back to objects again.
JavaScript functions are more then just some named pieces of code that implements certain behavior. They also can become objects themselves by a magic of the new
operator. To make things even more intriguing, the function calls can have memories, which will be explained in the section about closures.
If a function is meant to be instantiated with the new
operator it’s called a constructor function. If you are familiar with Java or C# you understand the concept of a class constructor that is being executed only once during the instantiation of a class. Now imagine that there is only a constructor without any class declaration that still can be instantiated with the new
operator as in the following example.
function Person(lname, fname, age){
this.lastName=lname;
this.firstName=fname;
this.age=age;
};
// Creating 2 instances of Person
var p1 == new Person(“Roberts”,“Julia”, 42);
var p2 == new Person(“Smith”, “Steven”, 34);
This code declares the function Person
and after that, withe the help of the new operator it creates two instances of the Person
object referred by the variables p1
and p2
accordingly.
According to common naming conventions the names of the constructor functions are capitalized.
Note
|
The JavaScript language doesn’t support classes, and a constructor function is the closest concept to the classes of the languages like Java or C#. Chapter 4 is about the Ext JS framework that extends JavaScript and introduces constructs similar to classes and classical inheritance. |
Objects can have methods and properties, right? On the other hand, functions are objects. Hence functions can have methods and properties too. If you declare a function marryMe()
inside the constructor function Person
, marryMe()
becomes a method of Person
. This is exactly what we’ll do next. But this time we’ll create an HTML file that includes the <script>
section referring to the JavaScript code sample located in a separate file.
If you want to try it hands-on, create a new file in your Aptana project by selecting the menu File | New | File and give it a name marryme.js. When prompted, accept the suggested default JavaScript template, and key in the following content into this file:
function Person(lname, fname, age){
this.lastName=lname;
this.firstName=fname;
this.age=age;
this.marryMe=function(person){
console.log("Will you marry me, " + person.firstName);
};
};
var p1== new Person("Smith", "Steven");
var p2== new Person("Roberts", "Julia");
p1.marryMe(p2);
The code above uses the keyword this
that refers to the object where the code will execute. If you are familiar with the meaning of this
in Java or C#, it’s similar, but not exactly the same, and we’ll illustrate it in the section titled "Who’s this". The method marryMe()
of one Person
object takes an instance of another Person
object and makes an interesting proposition: "Will you marry me, Julia".
This time we won’t run this code in the Firebug’s console, but rather will include it in the HTML file. In Aptana, create a new File | New | File, enter marryme.html as the file name and press the button Finish. Don’t press the button Next as it’ll offer you to select from one of the HTML templates, but this would generate lots of HTML content, which is not needed for our code sample. Just type in the following in the newly created empty file marryme.html.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>Making Proposal</h1>
<script src="marryme.js"></script>
</body>
</html>
In Aptana, right-click on the file marryme.html and select the option Run As | JavaScript Web Application. We continue using Firefox as Aptana’s default browser, and you’ll see it open a new Web page that reads "Making Proposals". Open the Firebug using the View menu of Firefox, refresh the page and switch to the Firebug’s tab Script. You’ll see the split panel with the JavaScript code from marryme.js on the left.
Let’s set a breakpoint inside the method marryMe()
by clicking in the Firebug’s gray area to the left of the line 7. You’ll see a red circle that will reveal a yellow triangle as soon as your code execution will hit this line. Refresh the content of the browser to re-run the script with a breakpoint. Now the execution stopped at line 7, the right panel contains the runtime information about the objects and variables used by your program.
On the top of the left panel you’ll see standard debugger buttons with curved arrows (Step Into, Step Over, Step Out) as well as triangular button to continue code execution. The right panel depicts the information related to this
and global Window
objects. In Firebug’s Script panel at a breakpoint this
represents the instance of the Person object represented by the variable p1 (Steven Smith). To see the content of the object, received by the method marryMe()
you can add the watch variable by clicking on the text "New watch expression…" and entering person
- the name of the parameter of marryMe()
. Firebug’s Script panel at a breakpoint shows the watch variable person
(Julia Roberts) that was used during the invocation of the method marryMe()
.
Now click on the Firebug’s Net panel, which shows what goes over the network during communication between the Web browser and Web server. Figure 2-11 shows a screen shot of the Net panel where we clicked on the Headers tab for marryme.html and the Response tab of marryme.js. The code 200 for both files means that they arrived successfully to the browser. It also shows the IP address of the Web server they came from, their sizes, and plenty of other useful information. Both Script and Net panels of Firebug or any other developers tools are your best friends of any Web developer.
We like Firebug, but testing and debugging should be done in several Web browsers. Besides Firebug, we’ll be using excellent Google Chrome developers tools. Their menus and panels are similar and we won’t be including such mini-tutorials on using such tools - you can easily learn them on your own.
Tip
|
You can find a tutorial on using Google Chrome Developer Tools at https://developers.google.com/chrome-developer-tools/. The cheatsheet of Chrome developer Tools is located at http://anti-code.com/devtools-cheatsheet/. Finally, Google offers an online video course titled "Explore and Master Chrome DevTools". |
A JavaScript array is a grab bag of any objects. You don’t have to specify in advance the number of elements to store, and there is more than one way to create and initialize array instances. The following code samples are self-explanatory.
var myArray=[];
myArray[0]="Mary";
myArray[2]="John";
// prints undefined John
console.log(myArray[1] + " " + myArray[2]);
var states1 == ["NJ", "NY", "CT", "FL"];
var states == new Array(4); // size is optional
states[0]="NJ";
states[1]="NY";
states[2]="CT";
states[3]="FL";
// remove one array element
delete states[1];
// prints undefined CT length=4
console.log(states[1] + " " + states[2] + " Array length=" + states.length);
// remove one element starting from index 2
states.splice(2,1);
// prints undefined FL length=3
console.log(states[1] + " " + states[2] + " Array length=" + states.length);
Removing elements with delete
creates gaps in the arrays while using the array’s method splice() allows to remove or replace the specified range of elements closing gaps.
The next code sample illustrates an interesting use case when we assign a string and a function text as array elements to mixedArray
. During array initialization the function promt()
will be invoked, the user will be prompted to enter name, and after that, two strings will be store in mixedArray
, for example "Hello" and "Mary".
var mixedArray=[
"Hello",
prompt("Enter your name", ”Type your name here")
];
JavaScript doesn’t support classes, at least till the ECMAScript 6 will become a reality. But JavaScript allows you to create objects that inherit properties and methods of other objects. By default, all JavaScript objects are inherited from Object
. Each JavaScript construction function has a special property called prototype
, which points at this object’s ancestor. If you want to create an inheritance chain where an instances of constructor function ObjectB
extends ObjectA
(as in classical object-oriented languages) just write one line of code: ObjectB.prototype=ObjectA;
.
Consider two constructor functions Employee
and Person
shown in the code snippet below.They represent two unrelated objects. But assigning the Person
object to the prototype
property of Employee
creates an inheritance chain, and now the object emp
will have all properties defined in both Employee
and Person
.
function Person(name, title){
this.name=name;
this.title=title;
this.subordinates=[];
}
function Employee(name, title){
this.name=name;
this.title=title;
}
// All instances of Employee will extend Person
Employee.prototype == new Person(); // (1)
var emp == new Employee("Mary", "Specialist"); // (2)
console.log(emp); // (3)
-
Assigning an ancestor of type person
-
Instantiating Employee
-
Printing the object referred by
emp
will output [object Object]. It happens because each object has a methodtoString()
, and if you want it to output useful information - override it. You’ll see how to do it later in this section.
We want to stress, that the property prototype
exists on constructor functions. After creating specific instances of such objects you may see that these instances have another property called proto
. At the time of this writing this property is not a standard yet and won’t be supported in some older browsers, bit ECMAScript 6 will make it official. To illustrate the difference between prototype
and proto
let’s add the following piece of code to the above sample:
//Create an instance of Person and add property dependents
var p=new Person();
p.dependents=1; // (1)
var emp2=new Employee("Joe", "Father");
//This employee will have property dependents
emp2.__proto__=p; // (2)
console.log("The number of Employee's dependents " + emp2.dependents); // (3)
-
Creating an instance of
Person
and adding an extra property dependents just for this instance -
Assigning this instance to the
proto
property of one instance -
The code will properly print 1 as a number of dependents of the
Employee
instance represented by the variableemp2
. The variableemp
from the previous code snippet won’t have the propertydependents
.
To try it hands-on, open the file WhoIsYourDaddy.html (it’s included in book code samples) in Aptana. Just for a change, this time we’ll use Google Chrome Developer Tools by opening the menu View | Developer | Developer Tools. Set the breakpoint at the last line of the JavaScript, refresh the Web page content, and add the watch expressions for the variables p
, emp
, and emp2
. When the JavaScript code engine runs into emp2.dependents
it tries to find this property in property on the Employee
object. If not found, the engine checks all the objects in the prototypal chain (in our case it’ll find it in the object p
) all the way up to the Object
if need be.
Tip
|
If you need to do some programmatic manipulations with only those properties that are defined on the specific object (not in its ancestors in the prototyal chain) do the check with the method hasOwnProperty() .
|
Please note the difference in the content of the variables proto
of the instances represented by emp
and emp2
. These two employees are inherited from two different objects Person
. Isn’t it scary? Not really.
Prototypical inheritance allows you to inherit one object from another, but it can lead to issues of redundancy and code duplication. If you take a closer look at the screenshot from The instance-specific proto
variable you’ll see that the Person
and Employee
objects have redundant properties name
and title
. We’ll deal with this redundancy in the section titled "Call and Apply". But first let’s introduce and cure the redundancy in method declarations when the prototypal inheritance is used.
Let’s add a method to addSubordinate()
to the ancestor object Person
that will populate its array subordinates
. Who knows, maybe an object Contractor
(descendant of a Person
) will need to be added in the future, so the ancestor’s method addSubordinate()
can be reused. First, we’ll do it the wrong way to illustrate the redundancy problem, and then we’ll do it right. Consider the following code:
// Constructor function Person
function Person(name, title){
this.name=name;
this.title=title;
this.subordinates=[];
// Declaring method inside the constructor function
this.addSubordinate=function (person){
this.subordinates.push(person)
}
}
// Constructor function Employee
function Employee(name, title){
this.name=name;
this.title=title;
}
// Changing the inheritance of Employee
Employee.prototype == new Person();
var mgr == new Person("Alex", "Director");
var emp1 == new Employee("Mary", "Specialist");
var emp2 == new Employee("Joe", "VP");
mgr.addSubordinate(emp1);
mgr.addSubordinate(emp2);
console.log("mgr.subordinates.length is " + mgr.subordinates.length);
The method addSubordinate()
here is declared inside the constructor function Person
, which becomes an ancestor of the Employee
. After instantiation of two Employee
objects the method addSubordinate()
is duplicated for each instance.
Let’s use Google Chrome Developer Tools profiler to see the sizes of the objects allocated on the Heap memory. But first we’ll set up two breakpoints - one before, and one after creating our instances as shown on Preparing Breakpoints Take 1..
When the execution of the code will stop at the first breakpoint, we’ll switch to the Profiler tab and take the first Heap snapshot. Upon reaching the second breakpoint we’ll take another Heap snapshot. The dropdown at the status bar allows to view the objects allocated between the snapshots 1 and 2. Objects allocated between snapshots 1 and 2 depicts this view of the profiler. Note that the total size (the Shallow Size column) for the Person
instances is 132 bytes. Employee
instances weigh 104 bytes.
Now we’ll change the code to declare the method not inside the Person
constructor function, but on it’s prototype - and this is the right way to declare methods in functions to avoid code duplication.
// Constructor function Person
function Person(name, title){
this.name=name;
this.title=title;
this.subordinates=[];
}
//Declaring method on the object prototype
Person.prototype.addSubordinate=function(subordinate){
this.subordinates.push(subordinate);
return subordinate;
}
// Constructor function Employee
function Employee(name, title){
this.name=name;
this.title=title;
}
// Changing the inheritance of Employee
Employee.prototype == new Person();
var mgr == new Person("Alex", "Director");
var emp1 == new Employee("Mary", "Specialist");
var emp2 == new Employee("Joe", "VP");
mgr.addSubordinate(emp1);
mgr.addSubordinate(emp2);
console.log("mgr.subordinates.length is " + mgr.subordinates.length);
Similarly, we’ll set up two breakpoints before and after object instantiation as shown in <<>FIG2-16>.
Let’s take two more profiler snapshots upon reaching each of the breakpoint. While the weight of the Employee
instances remained the same (104 bytes), the Person
instances became lighter: 112 bytes. While 20 bytes may not seem like a big deal, if you’ll need to create hundreds or thousands of object instances it adds up.
So if you need to declare a method on the object that will play a role of the ancestor, do it on the prototype level. The only exception to this rule is the case when such method needs to use some object specific variable that’s different for each instance - in case declare methods inside the constructors (see the section on closures for details).
Note
|
All modern Web browsers support the function Object.create() , which creates a new object based on another prototype object creates a new object and sets that new object’s prototype to be the object passed in. For example, var objectB=Object.create(objectA); . What if you must support an older browser and need such "create by example" functionality? Of course, you can always create a custom arbitrarily named function with the similar functionality as the latest implementation of Object.create() . But the future-proof approach is to create the missing methods with the same signatures and on the same objects as the latest ECMAScript specification prescribes. In case of Object.create() you can use the implementation offered by Douglas Crockford:
|
if (typeof Object.create !=== 'function') {
Object.create == function (o) {
function F() {}
F.prototype == o;
return new F();
};
}
newObject == Object.create(oldObject);
Such approach of custom implementation of missing pieces according to the latest ECMAScript specifications or W3C drafts is known as polyfills. People who can’t wait till the browser vendors will implement the newest functionality create cross-browser polyfills and some of them submit their source code to the public domain. You can find a number of polyfills in the git repository of the Modernizr project. The Web site http://caniuse.com/ contains the current information about browser’s support of the latest HTML5, JavaScript, and CSS features.
Tip
|
In the Ext JS chapter you’ll see how this frameworks offers its own class system that supports inheritance. |
Method overriding allows a subclass to replace (override) the functionality of a method defined in a superclass. Since JavaScript allows declaring methods on an object as well as on its prototype, overriding a method becomes really simple. The following code sample declares the method addSubordinate()
on the prototype of the Person
object, but then the object p1
overrides this method.
function Person(name, title){
this.name=name;
this.title=title;
this.subordinates=[];
}
Person.prototype.addSubordinate=function(person){
this.subordinates.push(person);
console.log("I'm in addSubordinate on prototype " + this);
}
var p1=new Person("Joe", "President");
p1.addSubordinate=function(person){
this.subordinates.push(person);
console.log("I'm in addSubordinate in object " + this);
}
var p2 == new Person("Mary", "Manager")
p1.addSubordinate(p2);
Running the above code prints only one line: "I’m in addSubordinate in object [object Object]". This proves that the method addSubordinate()
on the prototype level is overridden. We can also improve this example a little bit and override the method toString()
on the Person
. Just add the following fragment to the prior to instantiating p1
.
Person.prototype.toString=function(){
return "name:" + this.name +" title:" + this.title;
}
Now the code prints "I’m in addSubordinate in object name:Joe, title:President". Overriding the method toString()
on objects is a common practice as it gives a textual representation of your objects.
You are about to read one of the most confusing sections in this book. The confusion is caused by some inconsistencies in JavaScript design and implementations by various browsers. Do you know what will happen if you’ll remove the keywords this
from the toString()
method from previous section? You’ll get an error - the variable title
is not defined. Without the keyword this
the JavaScript engine tries to find the variable title
in the global namespace. Declaring and initializing the variable title
outside of the Person
declaration get rid of this error, but this is not what we want to do. Misunderstanding of the current scope can lead to difficult to debug errors.
Caution
|
Interestingly enough replacing this.name with name doesn’t generate an error, but rather initializes the variable name with an empty string. Although name is not an officially reserved JavaScript keyword, there are articles in the blogosphere that don’t recommend using the word name as a variable name. Keep this list of reserved words handy to avoid running into an unpredictable behavior.
|
Let’s consider several examples that will illustrate the meaning of this
variable in JavaScript. The code sample below defines an object myTaxObject
and calls its method doTaxes()
. Notice two variables with the same name taxDeduction
- one of them has global scope and another belongs to myTaxObject
. This little program was written for mafia and will apply some under the table deduction for the people who belong to Cosa Nostra.
var taxDeduction=300; // global variable
var myTaxObject == {
taxDeduction: 400, // object property
doTaxes: function() {
this.taxDeduction +== 100;
var mafiaSpecial== function(){
console.log( "Will deduct " + this.taxDeduction);
}
mafiaSpecial(); // invoking as a function
}
}
myTaxObject.doTaxes(); //invoking method doTaxes
This code fragment illustrates the use of nested functions. The object method doTaxes()
has a nested function mafiaSpecial()
, which is not visible from outside of the myTaxObject
, but it can be certainly invoked inside doTaxes()
. What number do you think this code will print after the words "Will deduct "? Will it print three, four, or five hundred? Run this code in Firebug, Chrome Developer Tools or any other way and you’ll see that it’ll print 300!
But this doesn’t sound right, does it? The problem is that in JavaScript the context where the function executes depends on the way it was invoked. In this case the function mafiaSpecial()
was invoked as a function (not a method) without specifying the object it should apply to, and JavaScript makes it operate in the global object, hence the global variable taxDeduction
having the value of 300 is being used. So in expression this.taxDeduction
the variable this
means global unless the code is operated in the strict mode.
Note
|
ECMAScript 5 introduced a restricted version of JavaScript called strict mode, which among other things places stricter requirements to variable declarations and scope identification. Adding "use strict" as the first statement of the method doTax() will make the context undefined, and it’ll print the error "this is undefined" and not 300. You can read about the strict mode at Mozilla’s developers site.
|
Let’s make a slight change to this example and take to control what this
represents. When the object myTaxObject
was instantiated its own this
reference was created. The following code fragment stores this reference in additional variable thisOfMyTaxObject
changes the game and the expression thisOfMyTaxObject.taxDeduction
evaluates to 500.
var taxDeduction=300; // global variable
var myTaxObject == {
taxDeduction: 400, // object property
doTaxes: function() {
var thisOfMyTaxObject=this;
this.taxDeduction +== 100;
var mafiaSpecial== function(){
console.log( "Will deduct " + thisOfMyTaxObject.taxDeduction);
}
mafiaSpecial(); // invoking as a function
}
}
myTaxObject.doTaxes(); //invoking method doTaxes
You’ll see a different way of running a function in the context of the specified object using special functions call()
and apply()
. But for now consider one more attempt to invoke mafiaSpecial()`shown in the following example that uses `this.mafiaSpecial()
notation.
var taxDeduction=300; // global variable
var myTaxObject == {
taxDeduction: 400, // object property
doTaxes: function() {
this.taxDeduction +== 100;
var mafiaSpecial== function(){
console.log( "Will deduct " + this.taxDeduction);
}
this.mafiaSpecial(); // trying to apply object's scope
}
}
myTaxObject.doTaxes(); //invoking method doTaxes
Run the above code and it’ll give you the error "TypeError: this.mafiaSpecial is not a function" and rightly so. Take a closer look at the object myTaxObject
represented by the variable this
. The myTaxObject
has only two properties: taxDeduction
and doTaxes
. The function mafiaSpecial
is hidden inside the method doTaxes
and can’t be accessed via this
.
After learning how to hide a a function inside an object, let’s see how to do something quite opposite - allowing an external method to run inside the context of an object.
Visualize the International Space Station, and add to the picture an image of a approaching space shuttle. After attaching to the docking bay of the station the shuttle’s crew performs some functions on the station (a.k.a. object) and then flies to another object or back to Earth. What it has to do with JavaScript? It can serve as an analogy for creating a JavaScript function that can operate in the scope of any arbitrary object. For this purpose JavaScript offers two special functions: call()
or apply()
. Both call()
and apply()
can invoke any function on any object. The only difference between them is that apply()
passes parameters to a function as an array, while call()
uses a comma-separated list.
Tip
|
Every function in JavaScript is an instance of the Function object. Both call() and apply() are defined in Function .
|
For example, a function calcStudentDeduction(income,numOfStudents)
can be invoked in a context of a given object using either call() or apply(). Note that with call()
parameters have to be listed explicitly, while with apply
parameters are given as an array:
calcStudentDeduction.call(myTaxObject, 50000, 2);
calcStudentDeduction.apply(myTaxObject, [50000, 2]);
In the above example the instance of myTaxObject
can be referred as this
from within the function calcStudentDeduction()
even though this is a function and not a method. The last example from the previous section can be re-written to invoke mafiaSpecial()
. The following code will ensure that mafiaSpecial()
has this
pointing to `myTaxObject' and will print on the console "Will deduct 500".
var taxDeduction=300; // global variable
var myTaxObject == {
taxDeduction: 400,
doTaxes: function() {
this.taxDeduction +== 100;
var mafiaSpecial== function(){
console.log( "Will deduct " + this.taxDeduction);
}
mafiaSpecial.call(this); // passing context to a function
}
}
myTaxObject.doTaxes();
Can you live without using call()
and apply()
? Sure you can, but JavaScript allows you to easily create callbacks. The callback mechanism lets you pass the code of one function as a parameter to another function for execution in the latter function’s context. This is a very useful feature of the language. Imagine an object with a method processData()
. Depending on the business logic you can pass to this method (as an argument) different functions that will do actual data processing - these are callbacks.
Another example of callbacks is event handlers. If a user clicks on this button here’s the name of the handler function to call:
`myButton.addEventListener("click", myFunctionHandler);`
It’s important to understand that you don’t not immediately call the function myFunctionHandler
here - you are just registering it as the function argumrnt. If the user will click on myButton
then the code of the callback myFunctionHandler
will be given to the object myButton
and will be invoked in the context of the myButton
object. The functions call()
and apply()
exist exactly for this purpose.
Let’s consider an example when you need to write a function that will take two arguments: an array containing preliminary tax data and a callback function, which will be applied to each element of this array. The following code sample creates myTaxObject
that has two properties: taxDeduction
and the applyDeduction
. The latter is a method with two parameters:
var myTaxObject == {
taxDeduction: 400, // state-specific deduction
// this function takes an array and callback as parameters
applyDeduction: function(someArray, someCallBackFunction){
for (var i == 0; i < someArray.length; i++){
// Invoke the callback
someCallBackFunction.call(this, someArray[i]);
}
}
}
// array
var preliminaryTaxes=[1000, 2000, 3000];
// tax handler function
var taxHandler=function(currentTax){
console.log("Hello from callback. Your final tax is " +
(currentTax - this.taxDeduction));
}
// invoking applyDeduction passing an array and callback
myTaxObject.applyDeduction(preliminaryTaxes, taxHandler);
The above code invokes applyDeduction()
passing it the array preliminaryTaxes
and the callback function taxHandler
that takes the currentTax
and subtracts this.taxDeduction
. By the time this callback will be applied to each element of the array the value of this
will be known and this code will print the following:
Hello from callback. Your final tax is 600
Hello from callback. Your final tax is 1600
Hello from callback. Your final tax is 2600
You may be wondering, why passing the function to another object if we could take an array, subtract 400 from each of its elements and be done with it? The solution with callbacks gives you an ability to make the decision on what function to call during the runtime and call it only when a certain event happens. Callbacks allow you to do asynchronous processing. For example, you make an asynchronous request to a server and register the callback to be invoked if a result comes back. The code is not blocked and doesn’t wait until the server response is ready. Here’s an example from AJAX: request.onreadystatechange=myHandler
. You register myHandler
callback but not immediately call it. JavaScript functions are objects, so get used to the fact that you can pass them around as you’d be passing any objects.
A variable scope depends on where it was declared. You already had a chance to see that a variable declared inside a function with the keyword var
is visible only inside this function and any function declared within it. Some programming languages allow to narrow down the scope even further. For example, in Java declaring a variable inside any block of code surrounded with curly braces makes it visible only inside such a block. In JavaScript it works differently. No matter where in the function you declared the variable its declaration will be hoisted to the top of the function, and you can use this variable anywhere inside the function.
The following code snippet will print 5 even though the variable b has been declared inside the if-statement. It’s declaration has been hoisted to the top:
function test () {
var a=1;
if(a>0) {
var b == 5;
}
console.log(b);
}
test();
Let’s make a slight change to the above code to separate the variable declaration and initialization. The following code has to console.log(b)
statements. The first one will output undefined
and the second will print 5 just as in the previous example.
function test () {
var a=1;
console.log(b); // b is visible, but not initialized
if(a>0) {
var b;
}
b=5;
console.log(b); // b is visible and initialized
}
test();
Due to hoisting, JavaScript doesn’t complain when the first console.log(b)
is invoked. It knows about the variable b
, but its value is undefined
just yet. By the time the second console.log(b)
is called, the variable b was initialized with the value of 5. Just remember that hoisting just applies to variable declaration and doesn’t interferes with your code when it comes to initialization.
JavaScript function declarations are hoisted too, and this is illustrated in the following code sample.
function test () {
var a=1;
if(a>0) {
var b;
}
b=5;
printB();
function printB(){
console.log(b);
}
}
test();
This code will print 5. We can call the function printB() here because its declaration was hoisted to the top. But the situation changes if instead of function declaration we’ll use the function expression. The following code will give you an error "PrintB is not a function".
function test () {
var a=1;
if(a>0) {
var b;
}
b=5;
printB();
var printB == function(){
console.log(b);
}
}
test();
Notice that it the error doesn’t complain about printB
being undefined cause the variable declaration was hoisted, but since the function expression wasn’t the JavaScript engine doesn’t know yet that printB
will become a function really soon. Anyway, moving the invocation line printB()
to the bottom of the function test()
cures this issue.
Tip
|
Function expressions are not being hoisted, but the variables they are assigned to (if any) are being hoisted. |
Functions as any other objects can have properties. You can attach any properties to a Function
object and their values can be used by all instances of this object. Static variables in programming languages with the classical inheritance is the closest analogy to function properties in JavaScript.
Let’s consider an example of a constructor function Tax
. An accounting program can create multiple instances if Tax
- one per person. Say this program will be used in a Florida neighborhood with predominantly Spanish speaking people. The following code illustrates the case when the method doTax()
can be called with or without parameters.
function Tax(income, dependents){
this.income=income; // instance variable
this.dependents=dependents; // instance variable
this.doTax == function calcTax(state, language){
if(!(state && language)){ // (1)
console.log("Income: " + this.income + " Dependents: "+ this.dependents
+ " State: " + Tax.defaults.state + " language:" + Tax.defaults.language);
} else{ // (2)
console.log("Income: " + this.income + " Dependents: "+ this.dependents
+ " State: " + state + " language:" + language);
}
}
}
Tax.defaults={ // (3)
state:"FL",
language:"Spanish"
};
// Creating 2 Tax objects
var t1 == new Tax(50000, 3);
t1.doTax(); // (4)
var t2 == new Tax(68000, 1);
t2.doTax("NY","English"); // (5)
-
No state and language were given to the method
doTax()
-
The state and language were provided as
doTax()
parameters -
Assigning the object with two properties as a
defaults
property onTax
. The propertydefault
is not instance specific, which makes it static. -
Invoking
doTax()
without parameters - usedefaults
-
Invoking
doTax()
with parameters
This program will produce the following output:
Income: 50000 Dependents: 3 State: FL language:Spanish
Income: 68000 Dependents: 1 State: NY language:English
You can add as many properties to the constructor function as needed. For example, to count the number of instances of the Tax
object just add one more property Tax.counter=0;
. Now add to the Tax
function something like console.log(Tax.counter++);
and you’ll see that the counter increments on each instance creation.
Tip
|
If multiple instances of a function object need to access certain HTML elements of the DOM, add references to these elements as function properties so objects can reuse them instead of traversing the DOM (it’s slow) from each instance. |
A closure is one of those terms that are easier explained by examples. Closures have formal definitions, which are not very helpful for the first timers. Here’s the definiton of a closure from Wikipedia:
"In programming languages, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function. A closure—unlike a plain function pointer—allows a function to access those non-local variables even when invoked outside its immediate lexical scope."
It’s not a very helpful definition, is it? Let’s try to give a better one. Imagine a function that contains a private variable, and a nested function. Is it possible to invoke the nested function from the outside of the outer one? And if it’s possible, what this inner function knows about its surroundings?
Larry Ullman, gives the following definition in his book "Modern Java Script": "Closure is a function call with memory". We can offer you our version of what a closure is: "Closure is a function call with strings attached".
In classical object-oriented languages you create an object with a certain state and behavior and can pass if to a method of another object for further processing. In JavaScript you can pass even a function to some object’s method for further processing. But what if a function also need to remember the state (the values of external variables) of the context where the function was defined?
Think of a closure as a function that remembers state - it’s just a special type of object that can be passed between objects and use certain variables that didn’t seem to be defined in the function’s code. But there existed in the context where the the function was defined.
===Closures by Example
Now it’s time for the explanation of these mysterious definitions, and we’ll do it by example. Consider the following code that is yet another example of implementing tax collection functionality.
(function (){ // this is an anonymous function expression
var taxDeduction == 500; // private context to remember
//exposed closure
this.doTaxes=function(income, customerName) {
var yourTax;
if (customerName !== "Tony Soprano"){
yourTax == income*0.05 - taxDeduction;
} else{
yourTax == mafiaSpecial(income);
}
console.log( " Dear " + customerName + ", your tax is "+ yourTax);
return yourTax;
}
//private function
function mafiaSpecial(income){
return income*0.05 - taxDeduction*2;
}
})(); // Self-invoked function
// The closure remembers its context with taxDeduction=500
doTaxes(100000, "John Smith");
doTaxes(100000, "Tony Soprano");
mafiaSpecial(); // throws an error - this function is private
First, a self-invoking function will create an anonymous instance of an object in the global scope. It contains a private variable taxDeduction
, a public method doTaxes()
, and a private method mafiaSpecial()
. Just by the virtue of declaring doTaxes
on this
object, this method becomes exposed to the current scope, which is global in this example.
After that we call the method doTaxes()
twice. Note that the function doTaxes()
uses the variable taxDeduction
that was never declared there. But when doTaxes
was initially declared, the variable taxDeduction
with a value of 500 was already there. So the internal function "remembers" the context (the neighborhood) where it was declared and can use it for its calculations.
The algorithm of tax calculations makes doTaxes()
calls the function mafiaSpecial()
if the customer’s name is "Tony Soprano". The function mafiaSpecial()
is not visible from outside, but for insiders like doTaxes()
it’s available. Here’s what the above code example will print on the console:
Dear John Smith, your tax is 4500
Dear Tony Soprano, your tax is 4000
Uncaught ReferenceError: mafiaSpecial is not defined
The Closure view in Chrome’s Developer Tools. shows the screenshot taken when doTaxes()
hit the breakpoint inside doTaxes
- note the right panel that shows what’s visible in the Closure scope.
Tip
|
JavaScript doesn’t give you an explicit way to mark an variable as private. By using closures you can get the same level of data hiding that you get from private variables in other languages. In the example above the variable taxDeduction is local for the object enclosed in the outermost parentheses and can’t be accessed from outside. But taxDeduction can be visible from the object’s functions doTaxws and mafiaSpecial .
|
Closure doTaxes gives yet another visual representation of our above code sample. The self-invoked anonymous function is shown as a cloud that exposes only one thing to the rest of the world: the closure doTaxes
.
Let’s consider a couple of more cases of returning a closure to the outside world so it can be invoked later. If the previous code sample was exposing the closure by using this.taxes
notation, the next two examples will simply return the code of the closure using the return
statement. The code below declares a constructor function Person
, adds a function doTaxes()
to its prototype, and finally creates two instances of the Person
calling the method doTaxes()
on each of them.
// Constructor function
function Person(name){
this.name == name;
}
// Declaring a method that returns closure
Person.prototype.doTaxes== function(){
var taxDeduction == 500;
//private function
function mafiaSpecial(income){
return income*0.05 - taxDeduction*2;
}
//the code of this function is returned to the caller
return function(income) {
var yourTax;
if (this.name !== "Tony Soprano"){
yourTax == income*0.05 - taxDeduction;
} else{
yourTax == mafiaSpecial(income);
}
console.log( "My dear " + this.name + ", your tax is "+ yourTax);
return yourTax;
}
}(); // important parentheses!
//Using closure
var p1 == new Person("John Smith");
var result1 == p1.doTaxes(100000);
var p2 == new Person("Tony Soprano");
var result2 == p2.doTaxes(100000);
The calculated taxes in this example are the same as in the previous one: John Smith has to pay $4500, while Tony Soprano only $4000. But we used different technique for exposing the closure. We want to make sure that you didn’t overlooked the parentheses at the very end of the function expression for doTaxes
. These parenthesis force the anonymous function to self-invoke itself, it’ll run into a return
statement and will assign the code of the anonymous inner function that takes parameter income
to the property doTaxes
. So when the line var result1 == p1.doTaxes(100000);
calls the closure the variable result1
will have the value 4500. Remove these important parentheses, and the value of result1
is not the tax amount, but the the code of the closure itself - the invocation of the closure is not happening.
The following code fragment is yet another example of returning the closure that remembers its context.
function prepareTaxes(studentDeductionAmount) {
return function (income) { // (1)
return income*0.05 - studentDeductionAmount;
};
}
var doTaxes == prepareTaxes(300); // (2)
var yourTaxIs == doTaxes(10000); // (3)
console.log("You tax is " + yourTaxIs); // (4)
-
When the function prepareTaxes is called, it immediately hits the
return
statement and returns the code of the closure to the caller. -
After this line is executed, the variable
doTaxes
has the code of the closure, which remembers thatstudentDeductionAmount
is equal to 300. -
This is actual invocation of the closure
-
the console output is "your tax is 200"
First, the closure is returned to the caller of prepareTaxes()
, and when the closure will be invoked it’ll remember the values defined in its outer context. After looking at this code you may say that there is nothing declared in the closure’s outside context! There is - by the time when the closure is created the value of the studentDeductionAmount
will be known.
Let’s revisit the code from the section Callbacks above. That code has shown how to pass an arbitrary function to another one and invoke it there using call()
. But if that version of the function taxHandler
was not aware of the context it was created in, the version below will. If in classical object-oriented languages you’d need to pass a method that knows about it’s context, you’d need to create an instance of an object that contains the method and the required object-level properties, and then you’d be passing this wrapper-object to another object for processing. But since the closure remembers its context anyway, we can just pass a closure as an object. Compare the code below with the code from the Callbacks section.
var myTaxObject == {
// this function takes an array and callback as parameters
applyDeduction: function(someArray, someCallBackFunction){
for (var i == 0; i < someArray.length; i++){
// Invoke the callback
someCallBackFunction.call(this, someArray[i]);
}
}
}
// array
var preliminaryTaxes=[1000, 2000, 3000];
var taxHandler == function (taxDeduction){
// tax handler closure
return function(currentTax){
console.log("Hello from callback. Your final tax is " +
(currentTax - taxDeduction));
};
}
// invoking applyDeduction passing an array and callback-closure
myTaxObject.applyDeduction(preliminaryTaxes, taxHandler(200));
The last line of the above example calls taxHandler(200)
, which creates a closure that’s being passed as a callback to the method applyDeduction()
. Even though this closure is executed in the context of myTaxObject
, it remembers that tax deduction is 200.
The need to extend capabilities of objects can be fulfilled by inheritance, but this is not the only way of adding behavior to objects. In this section you’ll see an example of something that would not be possible in the object-oriented languages like Java or C#, which don’t support multiple inheritance. JavaScript allows taking a piece of code and mix it into any object regardless of what its inheritance chain is. Mixin is a reusable code fragment that an object can borrow without the need to use inheritance. We’ll illustrate this concept by example.
In the next code fragment we’ll define a function expression and will assign it to a variable named Tax
. This is a closure that includes the function calcTax()
that knows the values of income
and state
. There is also an independent mixin TaxMixin
with a couple of functions mafiaSpecial()
and drugCartelSpecial()
. We want to blend in this mixin into Tax
. After this is is done, the Tax object will have its original functionality,i.e. calcTax()
, as well as a new "mafia and drug cartel flavors":
// Defining a function expession
var Tax == function(income, state){
this.income=income;
this.state=state;
this.calcTax=function(){
var tax=income*0.05;
console.log("Your calculated tax is " + tax)
return tax;
}
};
// Defining a mixin
var TaxMixin == function () {};
TaxMixin.prototype == {
mafiaSpecial: function(originalTax){
console.log("Mafia special:" + (originalTax - 1000));
},
drugCartelSpecial: function(originalTax){
console.log("Drug Cartel special:" + (originalTax - 3000));
}
};
// this function can blend TaxMixin into Tax
function blend( mainDish, spices ) {
for ( var methodName in spices.prototype ) {
mainDish.prototype[methodName] == spices.prototype[methodName];
}
}
// Blend the spices with the main dish
blend( Tax, TaxMixin );
// Create an instant of Tax
var t == new Tax(50000, "NY");
var rawTax == t.calcTax();
// invoke a freshly blended method
t.mafiaSpecial(rawTax);
The function blend()
loops through the code of the TaxMixin
and copies all its properties into Tax
. After the function blend()
is finished, you can call on the Tax
instance newly acquired methods mafiaSpecial()
and drugCartelSpecial()
.
Mixins can be useful is you want to provide a specific feature to a number of different objects without changing their inheritance chains. The other use case is if you want to prepare a bunch of small code fragments (think spices) and add any combination of them to the various objects (dishes) as needed. Mixins give you a lot of flexibility in what you can achieve with the minimum code, but they may decrease the readability of your code.
If you’ve read this far, you should have a good understanding of the syntax of the JavaScript language. Studying the code samples provided in this appendix has one extra benefit: now you can apply for a job as a tax accountant in a mafia near you.
After learning all these facts and techniques about the language you might be eager to see "the real-world use of JavaScript". Slowly but surely a Web browser becomes the leading platform for development of the user interface. The vast majority today’s JavaScript programs primarily manipulate HTML elements of Web pages. In this section we’ll be doing exactly this – applying JavaScript code to modify the content or style of HTML elements.
DOM stands for Document Object Model. It’s an object representing the hierarchy of HTML elements of a Web page. Every element of the HTML document is loaded into DOM. Each DOM element has a reference to its children and siblings. When DOM was invented, the Web pages were simple and static. DOM was not meant to be an object actively accessed by the code. This is the reason that on some of the heavily populated Web pages manipulating of DOM elements can be slow. Most likely DOM is the main target for anyone who’s trying to optimize the performance of a Web page.
Tip
|
If your Web page is slow, analyze it with YSlow, the tool built based on the Yahoo! rules for high performance Web sites. Also, you can minimize and obfuscate your JavaScript code with the help of JavaScript Compressor. |
When a Web Browser is receiving the content it performs the following activities:
-
Adding arriving HTML elements to DOM and laying out the content of the Web pages
-
Rendering of the UI
-
Running JavaScript that was included in the HTML
-
Processing events
The amount of time spent on each of these activities varies depending the content of the page.
Tip
|
If you are interested in learning how the browsers work in detail, read an excellent writeup titled "How Browsers Work: Behind The Scenes of Modern Web Browsers" at http://bit.ly/how-browsers-work [http://bit.ly/how-browsers-work]. |
Let’s consider the operations your application needs to be able to perform inside the Web page:
-
Programmatically finding the required element by id, type, or a CSS class
-
Changing styles of the elements (show, hide, apply fonts and colors et al.)
-
Processing events that may happen to HTML elements (
click
,mouseover
and the like) -
Dynamically adding or removing HTML elements from the page or changing their contents
-
Communicating with the server side, e.g. submitting forms or making AJAX requests for some data from the server
Now you’ll see some code samples illustrating the use of JavaScript for the operations listed above. Even if you’ll be using one of the popular JavaScript frameworks, your program will be performing similar operations applying the syntax prescribed by your framework of choice. So let’s learn how it can be done.
If you want to change the appearance of an HTML page, you need to manipulate with the DOM elements. Older Web applications were preparing the HTML content on the server side. For example, a server-side Java servlet would compose and send to the client HTML whenever the application logic required to change the appearance of the UI. The current trend is different - the client’s code takes care of the UI rendering, and only the data go back and forth between the client and the server. You’ll see how this works in more detail in Chapter 2 that explains the use of AJAX and JSON.
Earlier in this appendix we were talking about the global namespace where all JavaScript objects live unless they were declared with var
inside the functions. If the JavaScript code is running in a Web browser, this global namespace is represented by a special variable window
. It’s an implicit variable and you don’t have to use it in your code, but whenever we say that a variable is global, we mean that it’s exists on the window
object. For example, the code below will print "123 Main Street" twice:
var address ="123 Main Street";
console.log(address);
console.log(window.address);
The window
object has a number of useful properties like cookie
, location
, parent
, document
and others. The variable document
points at the root of the DOM hierarchy. Pretty often your JavaScript code would find an element in the DOM first, and then it could read or modify its content.
Firebug’s representation of DOM is a snapshot from Firebug showing the fragment of a DOM of a simple Web page mixins.html.
Below are some of the methods that exist on the document
object:
-
document.write(text)
– adds the specifies text to the DOM. Careless using of the methodwrite()
can result in unpredictable results if after changing the DOM the HTML content is still arriving. -
document.getElementById(id)
– get a reference to the HTML element by its unique identifier -
document.getElementsByTagName(tname)
- get a reference to one or more elements by tag names, e.g.get a reference to all<div>
elements. -
document.getElementsByName(name)
- get a reference to all elements that have requested value in theirname
attribute. -
document.getElementsByClassName(className)
– get a reference to all elements to use specified CSS class(es), likedocument.getElementsByClassName('red text-left')
. -
document.querySelector(cssSelector)
– Find the first element that matches provided CSS selector. string. It comes handy if you want to specify more complex queries that just a class name, e.g.document.querySelector("style[type='text-left']");
. -
document.querySelectorAll(cssSelector)
– Find all elements that match provided CSS selector string.
The next code sample contains the HTML <span>
element that has an id emp
. Initially it contains ellipsis, but when the user enters the name in the input text field, the JavaScript code will find the reference to this <span>
element and will replace the ellipsis with the content of the input text field.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Selecting DOM elements</h2>
<p>
The employee of the month is <span id="emp">...</span>
<br>
<input type="button" value="Change the span value"
onclick="setEmployeeOfTheMonth()"/>
Enter your name <input type="text" id="theName" />
</p>
<script>
function setEmployeeOfTheMonth(){
var mySpan == document.getElementById("emp");
var empName== document.getElementsByTagName("input")[1];
mySpan.firstChild.nodeValue== empName.value;
}
</script>
</body>
</html>
Note the input field of type button
, which includes the onclick
property that corresponds to the click
event. When the user clicks on the button, the browser dispatched click
event, and calls the JavaScript function setEmployeeOfTheMonth()
. The latter queries the DOM and finds the reference to the emp
by calling the method getElementBuId()
. After that, the method getElementByTagName()
is called trying to find all the references to the HTML <input>
elements. This methods returns an array cause there could be more than one element with the same tag name on a page, which explains the use of array notation. The first <input>
element is a button and the second is the text field we’re interested in. Remember that arrays in JavaScript have zero-based indexes. Changing the content of the HTML <span> element shows the Web page after the user entered the name Mary and pressed the button.
While manipulating the content of your Web page you may you may need to traverse the DOM tree. The code example below shows you an HTML document that includes JavaScript that walks the DOM and prints the name of each node. If a node has children, the recursive function walkTheDOM()
will visit each child.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>WalkTheDom.html</h1>
<p>
Enter your name: <input type="text"
name="customerName" id="custName" />
</p>
<input type="button" value="Walk the DOM"
onclick="walkTheDOM(document.body, processNode)"/>
<script>
function walkTheDOM(node, processNode){
processNode(node)
node == node.firstChild;
while(node){
// call wakTheDOM recursively for each child
walkTheDOM(node,processNode);
node == node.nextSibling;
}
}
function processNode(node){
// the real code for node processing goes here
console.log("The current node name is "+ node.nodeName);
}
</script>
</body>
</html>
Our function processNode()
just prints the name of the current node, but you could implement any code that your Web application requires. Run this code in different browsers and check the output on the JavaScript console. Traversing the DOM in Firefox depicts two snapshots taken in the F12 Developer Tools in Internet Explorer (left) and FIrebug running in Firefox (right).
While some of the output is self-explanatory, there is a number of #text
nodes that you won’t find in the code sample above. Unfortunately, Web browsers treat white-spaces in differently - some will ignore them, while others will report them as DOM elements. Accordingly, different browsers insert different number of text nodes in the DOM representing whitespaces found in the HTML document. So you’ll be better off using one of the JavaScript frameworks for traversing the DOM cross-browser way. For example, JQuery framework’s API for DOM traversing is listed at http://bit.ly/WXj2r2.
CSS stands for Cascading Style Sheets. During the last 15 years several CSS specifications reached the level of Recommendation by W3C: CSS Level 1, 2, and 2.1. The latest CSS Level 3 (a.k.a. CSS3) adds new features to CSS 2.1 module by module, which are listed at http://www.w3.org/Style/CSS/current-work.
Tip
|
You can find CSS tutorial as well as tons of other learning resources at webplatform.org. |
You can include CSS into a Web page by linking to separate files using the HTML tag <link>
, or by in-lining the styles with the tag <style>
, or using the style
attribute in HTML element (not recommended). For example, if CSS is located in the file mystyles.css
in
the folder css add the following tag to HTML:
<link rel="stylesheet" type="text/css" href="css/mystyles.css" media="all">
The <link>
tag allows specifying the media where specific css file has to be used. For example, you can have one CSS file for smartphones and another one for tablets. We’ll discuss this in detail in the section on media queries in Chapter 9.
You should put this tag in the section of your HTML before any JavaScript code to make sure that they stiles are loaded before the content of the Web page.
Placing the @import
attribute inside the <style>
tag allows to include styles located elsewhere:
<style>
@import url (css/contactus.css)
</style>
What’s the best way of including CSS in HTML? We recommend using CSS files. Keeping CSS in files separately from HTML and JavaScript makes the code more readable and reusable. You may argue that if your Web site consists of many files, the Web browser will have to make multiple round trips to your server just to load all resources required by the HTML document, which can worsen the responsiveness of your Web application. But usually all files are merged into one before deploying Web application in QA or production servers.
HTML documents are often prettyfied by using CSS class selectors, and you can switch them programmatically with JavaScript. Imagine that a <style>
section has the following definition of two class selectors badStyle
and niceStile
:
<style>
.badStyle{
font-family: Verdana;
font-size:small;
color:navy;
background-color:red;
}
.niceStyle{
font-family: Verdana;
font-size:large;
font-style:italic;
color:gray;
background-color:green;
}
</style>
Any of these class selectors can be used by one or more HTML elements, for example
<div id="header" class="badStyle">
<h1>This is my header</h1>
</div>
Imagine that some important event has happened and the appearance the <div>
styled as badStyle
should programmatically change to <niceStyle>. In this case we need to find the badStyle
element(s) first and change their style. The method getElementsByClassName()
returns a set of elements that have the specified class name, and since our HTML has only one such element, the JavaScript will use the element zero from such set:
document.getElementsByClassName("badStyle")[0].className="niceStyle";
The next example will illustrate adding a new element to the DOM. On click of a button the code below dynamically creates an instance of type img
and then assigns the location of the image to its src
element. In a similar way we could have assigned values to any other attributes of the img
element like width
, height
, or alt
. The method appendChild()
is applied to the <body> container, but it could be any other container that exists on the DOM.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Employee of the month</h2>
<p>
<input type="button" value="Show me"
onclick="setEmployeeOfTheMonth()"/>
</p>
<script>
function setEmployeeOfTheMonth(){
// Create an image and add it to the <body> element
var empImage=document.createElement("img");
empImage.setAttribute('src','resources/images/employee.jpg');
document.body.appendChild(empImage);
}
</script>
</body>
</html>
Tip
|
Some HTML elements like <div> or <span> contain other elements (children), and if you need to change their content use their property innerHTML . For example, to delete the entire content of the document body just do this: document.body.innerHTML="" . You can also use the method appendChild() as shown in the code sample above.
|
If you run this example and click on the button "Show me" you’ll see an image of the employee of the month added to the <body>
section of the HTML document as shown on After clicking the button "Show me".
Web browser will notify your application when some changes or interactions occur. In such cases the browser will dispatch an appropriate event, for example load
, unload
, mousemove
, click
, keydown
etc. When the Web page finished loading the browser will dispatch the load
event. When the user will click on the button on a Web page the click
event will be dispatched. A Web developer needs to provide JavaScript code that will react on the events important to the application. The browser events will occur regardless of if you provided the code to handle them or not. It’s important to understand some terms related to event processing.
An event handler (a.k.a. event listener) is a JavaScript code you want to be called as a response to this event. The last code sample from the previous section was processing the click
event on the button "Show me" as follows: onclick="setEmployeeOfTheMonth()"
.
Tip
|
Each HTML element has a certain number of predefined event attributes, which start with the prefix on followed by the name of the event. For example onclick is an event attribute that you can use for specifying the handler for the click event. You can find out what event attributes are available in the online document titled Document Object Model Events.
|
The preferred way of adding event listener was introduced in the DOM Level 2 specification back in 2000. You should find the HTML element in the DOM, and then assign the event listener to it by calling the method addEventListener()
(this is done differently in Internet Explorer below version 9). For example:
document.getElementById("myButton").addEventListener("click", setEmployeeOfTheMonth);
The advantage of using of such programmatic assignment of event listeners is that this can be done for all controls in a in a central place, for example in a JavaScript function that runs immediately after the Web page completes loading. Another advantage is that you can programmatically remove the event listener if it’s not needed any longer by invoking removeEventListener()
. The following example is a re-write of the last example from the previous section.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Employee of the month</h2>
<p>
<input type="button" value="Show me" id="myButton"/> <!--(1)-->
</p>
<script>
window.onload=function(){ // (2)
document.getElementById("myButton").addEventListener("click",
setEmployeeOfTheMonth);
}
function setEmployeeOfTheMonth(){
// Create an image and add it to the <body> element
var empImage=document.createElement("img");
empImage.setAttribute('src','resources/images/employee.jpg');
document.body.appendChild(empImage);
document.getElementById("myButton").removeEventListener("click",
setEmployeeOfTheMonth); // (3)
}
</script>
</body>
</html>
-
Compare this button with the one from the previous section: the event handler is removed, but it has an ID now.
-
When the Web page completes loading, a
load
event is dispatched and the function attached to the event attributeonload
assigns the event handler for the button 'click' event. Note that we are passing the callbacksetEmployeeOfTheMonth
as the second argument of theaddEventListener()
-
Removing the event listener after the image of the employee of the month has been added. Without this line each click on the button would add to the Web page yet another copy of the same image.
Each event goes through three different phases: Capture, Target, and Bubble. It’s easier to explain this concept by example. Imagine that a button is located inside the <div>
, which is located inside the <body>
container. When you click on the button, the event travels to the button through all enclosing containers, and this is the capture phase. You can intercept the event at one of these containers even before it reached the button if need be. For example, your application logic may need to prevent the button from being clicked if certain condition occurs.
Then event reaches the button, and it’s a target phase. After the event is handled by the button’s click
handler, the event bubbles up through the enclosing containers, and this is the bubble phase. you can create listeners and handle this event after the button finished its processing at the target phase. The next code sample is based on the previous one, but it demonstrates the event processing in all three phases.
Note that if your event handler function is declared with the event parameter, it’ll receive the Event
object (not in IE 8), which contains a number of useful parameters. For more information refer to the "Document Object Model Events" online.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Employee of the month</h2>
<div id="myDiv">
<input type="button" value="Show me" id="myButton"/>
</div>
<script>
window.onload=function(){
document.getElementById("myButton").addEventListener("click",
setEmployeeOfTheMonth);
document.getElementById("myDiv").addEventListener("click",
processDivBefore, true); // (1)
document.getElementById("myButton").addEventListener("click",
processDivAfter);
}
function setEmployeeOfTheMonth(){
console.log("Got the click event in target phase");
// Create an image and add it to the <body> element
var empImage=document.createElement("img");
empImage.setAttribute('src','resources/images/employee.jpg');
document.body.appendChild(empImage);
document.getElementById("myButton").removeEventListener("click",
setEmployeeOfTheMonth);
}
function processDivBefore(evt){
console.log("Intercepted the click event in capture phase");
// Cancel the click event so the button won't get it
// if (evt.preventDefault) evt.preventDefault(); (2)
// if (evt.stopPropagation) evt.stopPropagation();
}
function processDivAfter(){
console.log("Got the click event in bubble phase");
}
</script>
</body>
</html>
-
We’ve added two event handler on the
<div>
level. The first one intercepts the event on the capture phase. When the third argument ofaddEventListener()
is true, this handler will kick in during capture phase. -
If you uncomment these two lines, the default behavior if the
click
event will be cancelled and it won’t reach the button at all. Unfortunately, browsers may have different method implementing prevent default functionality hence additional if-statements are needed.
Running the above example will cause the following output in the JavaScript console:
Intercepted the click event in capture phase
Got the click event in target phase
Got the click event in bubble phase
You can see another example of using intercepting the event during the capture phase in the Donate Section of Chapter 2.
Tip
|
The Microsoft’s Web browsers Internet Explorer 8 and below didn’t implement the W3C DOM Level 3 event model - they handled events differently. You can read more on the subject at this MSDN article http://bit.ly/anZZgZ. |
This appendix covered the JavaScript language constructs that any professional Web developer should know. A smaller portion of this chapter was illustrating how to combine JavaScript, HTML, and CSS. There are lots of online resources and books that cover just the HTML markup and CSS, and you’ll definitely need to spend more time mastering details of the Web tools like Firebug or Google Developer Tools.
Software developers who are coming from strongly-typed compiled languages may have a feeling that their productivity drops with JavaScript. We can recommend several medications for this. FIrst, get familiar with the language called CoffeeScript. As a respected Java developer James Ward put it, "CoffeeScript is the way to write JavaScript". This language is very similar to JavaScript and is very easy to learn if you understand the JavaScript syntax, it supports classes and is compiled into JavaScript. Visit coffescript.org to see the CoffeeScript code snippets and their equivalents in JavaScript.
Another interesting language to learn is Microsoft’s TypeScript (it’s an open source project). This language is also an extension of JavaScript with added classes, interfaces and inheritance. If also gets compiled into JavaScript and allows developers write strongly-typed code. TypeScript increases productivity of developers because it helps identify lots of error related with incorrect types during the compilation phase. TypeScript implements many constructs from EcmaScript 6 and can be serve as an example of the JavaScript of the future.
Probably the most interesting new programming language is Google’s Dart. This is a compiled language with all object-oriented features - classes, objects, abstract classes, inheritance. The compiled code runs inside the VM, and Google supports it in Chrome browser. What about the other browsers? The Web application is deployed as a script that automatically checks if the the browser supports Dart. If it does, the compiled code will be sent to the client, otherwise the Dart code will be automatically compiled into JavaScript, and from the browser perspective nothing but a JavaScrpit engine is required. You can do a server-side programming in Dart too. JetBrains' WebStorm, our IDE of choice, supports Coffeescript, Dart, and Typescript.