Another RayJune

JavaScript 高级程序设计小记

The content is totally written in English, just for self review.

What Is JavaScript?

concepts

To begin down the path to using JavaScript’s full potential, it is important to understand its nature, history, and limitations.

Though JavaScript and ECMAScript are often used synonymously, JavaScript is much more than just what is defined in ECMA-262. Indeed, a complete JavaScript implementation is made up of the following three distinct parts

  • The Core (ECMAScript)
  • The Document Object Model (DOM)
  • The Browser Object Model (BOM)

(Other host environments include NodeJS, a server-side JavaScript platform, and Adobe Flash.)

summary

JavaScript is a scripting language designed to interact with web pages and is made up of the following three distinct parts:

  • ECMAScript, which is defined in ECMA-262 and provides the core functionality
  • The Document Object Model (DOM), which provides methods and interfaces for working with the content of a web page
  • The Browser Object Model (BOM), which provides methods and interfaces for interacting with the browser

(There are varying levels of support for the three parts of JavaScript across the five major web browsers (Internet Explorer, Firefox, Chrome, Safari, and Opera). Support for ECMAScript 3 is generally good across all browsers, and support for ECMAScript 5 is growing, whereas support for the DOM varies widely. The BOM, recently codified in HTML5, can vary from browser to browser, though there are some commonalities that are assumed to be available.)

JavaScript in HTML

In a word, just using the <script> element.

6 attributes

  • async — Optional. Indicates that the script should begin downloading immediately but should not prevent other actions on the page such as downloading resources or waiting for other scripts to load. Valid only for external script files. Comparing to defer, you should use defer first.
  • charset — Optional. The character set of the code specified using the src attribute. This attribute is rarely used, because most browsers don’t honor its value.
  • defer — Optional. Indicates that the execution of the script can safely be deferred until after the document’s content has been completely parsed and displayed. Valid only for external scripts. Internet Explorer 7 and earlier also allow for inline scripts.
1
2
3
4
5
6
7
8
9
10
<html>
<head>
<title>Example HTML Page</title>
<script type=”text/javascript” defer src=”example1.js”></script>
<script type=”text/javascript” defer src=”example2.js”></script>
</head>
<body>
<!-- content here -->
</body>
</html>

​ All other browsers simply ignore this attribute and treat the script as it normally would. For this reason, it’s still best to put deferred scripts at the bottom of the page. (For XHTML documents, specify the defer attribute as defer=”defer”.)

  • language — Deprecated. Originally indicated the scripting language being used by the code block (such as “JavaScript”, “JavaScript1.2”, or “VBScript”). Most browsers ignore this attribute; it should not be used.
  • src — Optional. Indicates an external file that contains code to be executed.
  • type — Optional. Replaces language; indicates the content type (also called MIME type) of the scripting language being used by the code block. Always use “text/javascript”.

line style

1
2
3
4
5
<script type=”text/javascript”>
function sayHi(){
alert(“Hi!”);
}
</script>

external style

1
<script type=”text/javascript” src=”example.js”></script>

In XHTML documents, you can omit the closing tag, as in this example:

1
<script type=”text/javascript” src=”example.js” />

This syntax should not be used in HTML documents, because it is invalid HTML and won’t be handled properly by some browsers, most notably Internet Explorer.

You should attention this situation:

1
<script type=”text/javascript” src=”http://www.somewhere.com/afile.js”></script>

(Code from an external domain will be loaded and interpreted as if it were part of the page that is loading it. This capability allows you to serve up JavaScript from various domains if necessary. Be careful, however, if you are referencing JavaScript files located on a server that you don’t control. A malicious programmer could, at any time, replace the file. When including JavaScript files from a different domain, make sure you are the domain owner or the domain is owned by a trusted source.)

Language Basics

Identifiers

  • The first character must be a letter, an underscore (_), or a dollar sign ($).
  • All other characters may be letters, underscores, dollar signs, or numbers.
  • Letters in an identifier may include extended ASCII or Unicode letter characters such as À and Æ, though this is not recommended.

variables

ECMAScript variables are loosely typed, meaning that a variable can hold any type of data. Every variable is simply a named placeholder for a value.

Data types

5 primitive types

  • Bollean
  • Number
  • String
  • Undefined
  • Null

1 complex data type

  • Object (which is an unordered list of name-value pairs)

The typeof Operator

Because ECMAScript is loosely typed, there needs to be a way to determine the data type of a given variable. The typeof operator provides that information. Using the typeof operator on a value returns one of the following strings:

  • “undefined” if the value is undefined
  • “boolean” if the value is a Boolean
  • “string” if the value is a string
  • “number” if the value is a number
  • “object” if the value is an object(including array) (other than a function) or null
  • “function” if the value is a function

Be aware there are a few cases where typeof seemingly returns a confusing but technically correct value. Calling typeof null returns a value of “object”, as the special value null is considered to be an empty object reference.

Technically, functions are considered objects in ECMAScript and don’t represent another data type. However, they do have some special properties, which necessitates differentiating between functions and other objects via the typeof operator.

The Undefined Type

When a variable is declared using var but not initialized, it is assigned the value of undefined.

The typeof operator returns “undefined” when called on an uninitialized variable, but it also returns “undefined” when called on an undeclared variable, which can be a bit confusing. Consider this example:

1
2
3
4
5
6
var message = undefined;
var age;

alert(typeof message); //”undefined”
alert(typeof age); //”undefined”
alert(typeof rayjune); //”undefined”

Null

Logically, a null value is an empty object pointer, which is why typeof returns “object” when it’s passed a null value.

1
2
var car = null; 
alert(typeof car); //”object”

The value undefined is a derivative of null, so ECMA-262 defines them to be superficially equal as follows:

1
alert(null == undefined); // "true"

but

1
alert(null === undefined); // "false"

Even though null and undefined are related, they have very different uses. As mentioned previously, you should never explicitly set the value of a variable to undefined, but the same does not hold true for null. Any time an object is expected but is not available, null should be used in its place. This helps tokeep the paradigm of null as an empty object pointer and further differentiates it from undefined.

The Boolean Type

The Boolean() casting function can be called on any type of data and will always return a Boolean value.

1
2
var message = “Hello world!”;
var messageAsBoolean = Boolean(message); //true

The rules for when a value is converted to false is that:

  • false
  • “” (empty string)
  • 0 (+0 & -0), NaN
  • null
  • undefined

The Number Type

Floating-Point Values

Because storing floating-point values uses twice as much memory as storing integer values, ECMAScript always looks for ways to convert values into integers. When there is no digit after the decimal point, the number becomes an integer. Likewise, if the number being represented is a whole number (such as 1.0), it will be converted into an integer, as in this example:

1
2
var floatNum1 = 1.; //missing digit after decimal - interpreted as integer 1 
var floatNum2 = 10.0;//whole number - interpreted as integer 10

For e-notation when number is

  • converts any floating- point value with at least 6 zeros after the decimal point into e-notation (for example, 0.0000003 becomes 3e-7).
  • and over 21 (for example, 1234567890123456789012 becomes 1.2345678901234568e+21)

Range of Values

Not all numbers in the world can be represented in ECMAScript, because of memory constraints. The smallest number that can be represented in ECMAScript is stored in Number.MIN_VALUE. and is 5e-324 on most browsers; the largest number is stored in Number.MAX_VALUE and is 1.7976931348623157e+308 on most browsers.

If a calculation results in a number that cannot be represented by JavaScript’s numeric range, the number automatically gets the special value of Infinity. Any negative number that can’t be represented is –Infinity (negative infinity), and any positive number that can’t be represented is simply Infinity (positive infinity).

If a calculation returns either positive or negative Infinity, that value cannot be used in any further calculations, because Infinity has no numeric representation with which to calculate. To determine if a value is finite (that is, it occurs between the minimum and the maximum), there is the isFinite() function. This function returns true only if the argument is between the minimum and the maximum values, as in this example:

1
2
var result = Number.MAX_VALUE + Number.MAX_VALUE; alert(isFinite(result)); //false
isFinie(NaN); //false

NaN

There is a special numeric value called NaN, short for Not a Number, which is used to indicate when an operation intended to return a number has failed (as opposed to throwing an error).

The value NaN has a couple of unique properties.

  • any operation involving NaN always returns NaN (for instance, NaN /10), which can be problematic in the case of multistep computations.
  • NaN is not equal to any value, including NaN. For example, the following returns false:
1
alert(NaN == NaN); //false

For this reason, ECMAScript provides the isNaN() function. This function accepts a single argument, which can be of any data type, to determine if the value is “not a number.”

1
2
3
4
5
alert(isNaN(NaN)); //true
alert(isNaN(10)); //false - 10 is a number
alert(isNaN(“10”)); //false - can be converted to number 10
alert(isNaN(“blue”)); //true - cannot be converted to a number
alert(isNaN(true)); //false - can be converted to number 1

and more:

Although typically not done, isNaN() can be applied to objects. In that case, the object’s valueOf() method is first called to determine if the returned value can be converted into a number. If not, the toString() method is called and its returned value is tested as well. This is the general way that built-in functions and operators work in ECMAScript and is discussed more in the “Operators” section later in this chapter.

Number Conversions

There are three functions to convert nonnumeric values into numbers: the Number() casting function, the parseInt() function, and the parseFloat()function. The first function, Number(), can be used on any data type; the other two functions are used specifically for converting strings to numbers. Each of these functions reacts differently to the same input.

For Number():

When applied to objects, the valueOf() method is called and the returned value is converted based on the previously described rules. If that conversion results in NaN, the toString() method is called and the rules for converting strings are applied.

The unary plus operator, discussed in the “Operators” section later in this chapter, works the same as the Number() function.

Because of the complexities and oddities of the Number() function when converting strings, the parseInt() function is usually a better option when you are dealing with integers.

The parseInt() function examines the string much more closely to see if it matches a number pattern. Leading white space in the string is ignored until the first non–white space character is found. If this first character isn’t a number, the minus sign, or the plus sign, parseInt() always returns NaN, which means the empty string returns NaN (unlike with Number(), which returns 0). If the first character is a number, plus, or minus, then the conversion goes on to the second character and continues on until either.

the end of the string is reached or a nonnumeric character is found. For instance, “1234blue” is converted to 1234 because “blue” is completely ignored. Similarly, “22.5” will be converted to 22 because the decimal is not a valid integer character.

1
2
3
4
5
6
var num1 = parseInt(“1234blue”); //1234
var num2 = parseInt(“”); //NaN
var num3 = parseInt(“0xA”); //10 - hexadecimal
var num4 = parseInt(22.5); //22
var num5 = parseInt(“70”); //70 - decimal
var num6 = parseInt(“0xf”); //15 - hexadecimal

All of the different numeric formats can be confusing to keep track of, so parseInt() provides a second argument: the radix (number of digits) to use. If you know that the value you’re parsing is in hexadecimal format, you can pass in the radix 16 as a second argument and ensure that the correct parsing will occur, as shown here:

1
var num = parseInt(“0xAF”, 16); //175

The parseFloat() function works in a similar way to parseInt(), looking at each character starting in position 0. It also continues to parse the string until it reaches either the end of the string or a character that is invalid in a floating-point number. This means that a decimal point is valid the first time it appears, but a second decimal point is invalid and the rest of the string is ignored, resulting in “22.34.5” being converted to 22.34.

Another difference in parseFloat() is that initial zeros are always ignored. This function will recognize any of the floating-point formats discussed earlier, as well as the decimal format (leading zeros are always ignored). Hexadecimal numbers always become 0. Because parseFloat() parses only decimal values, there is no radix mode. A final note: if the string represents a whole number (no decimal point or only a zero after the decimal point), parseFloat() returns an integer. Here are some examples:

1
2
3
4
5
6
var num1 = parseFloat(“1234blue”); //1234 - integer 
var num2 = parseFloat(“0xA”); //0
var num3 = parseFloat(“22.5”); //22.5
var num4 = parseFloat(“22.34.5”); //22.34
var num5 = parseFloat(“0908.5”); //908.5
var num6 = parseFloat(“3.125e7”); //31250000

The String Type

The String data type represents a sequence of zero or more 16-bit Unicode characters. Strings can be delineated by either double quotes (“) or single quotes (‘).

The length of any string can be returned by using the length property as follows:

1
alert(text.length); //outputs 28

This property returns the number of 16-bit characters in the string. If a string contains double-byte characters, the length property may not accurately return the number of characters in the string.

Converting to a String

There are two ways to convert a value into a string. The first is to use the toString() method that almost every value has. This method’s only job is to return the string equivalent of the value. Consider this example:

1
2
3
4
var age = 11;
var ageAsString = age.toString(); //the string “11”
var found = true;
var foundAsString = found.toString(); //the string “true”

The toString() method is available on values that are numbers, Booleans, objects, and strings. (Yes, each string has a toString() method that simply returns a copy of itself.) If a value is null or undefined, this method is not available.

In most cases, toString() doesn’t have any arguments. However, when used on a number value, toString() actually accepts a single argument: the radix in which to output the number. By default, toString() always returns a string that represents the number as a decimal, but by passing in a radix, toString() can output the value in binary, octal, hexadecimal, or any other valid base, as in this example:

1
2
3
4
5
6
var num = 10;    
alert(num.toString()); //”10”
alert(num.toString(2)); //”1010”
alert(num.toString(8)); //”12”
alert(num.toString(10)); //”10”
alert(num.toString(16)); //”a”

If you’re not sure that a value isn’t null or undefined, you can use the String() casting function, which always returns a string regardless of the value type. The String() function follows these rules:

  • If the value has a toString() method, it is called (with no arguments) and the result is returned.
  • If the value is null, error is returned.
  • If the value is undefined, error is returned.

You can also convert a value to a string by adding an empty string (“”) to that value using the plus operator

The Object Type

1
var o = new Object();

The Object type in ECMAScript is the base from which all other objects are derived. All of the properties and methods of the Object type are also present on other, more specific objects.

Each Object instance has the following properties and methods:

  • constructor — The function that was used to create the object. In the previous example, the constructor is the Object() function.
  • hasOwnProperty(propertyName) — Indicates if the given property exists on the object instance (not on the prototype). The property name must be specified as a string (for example, o.hasOwnProperty(“name”)).
  • isPrototypeOf(object) — Determines if the object is a prototype of another object.
  • propertyIsEnumerable(propertyName) — Indicates if the given property can be enumerated using the for-in statement. As with hasOwnProperty(), the property name must be a string.
  • toLocaleString() — Returns a string representation of the object that is appropriate for the locale of execution environment.
  • toString() — Returns a string representation of the object.
  • valueOf() — Returns a string, number, or Boolean equivalent of the object. It often returns the same value as toString().

( Technically speaking, the behavior of objects in ECMA-262 need not necessarily apply to other objects in JavaScript. Objects that exist in the browserenvironment, such as those in the Browser Object Model (BOM) and Document Object Model (DOM), are considered host objects since they are provided and defined by the host implementation. Host objects aren’t governed by ECMA-262 and, as such, may or may not directly inherit from Object. )

OPERATORS

ECMA-262 describes a set of operators that can be used to manipulate data values. The operators range from mathematical operations (such as addition and subtraction) and bitwise operators to relational operators and equality operators.

Operators are unique in ECMAScript in that they can be used on a wide range of values, including strings, numbers, Booleans, and even objects. When used on objects, operators typically call the valueOf() and/or toString() method to retrieve a value they can work with.

Unary Operators

Increment/Decrement

The difference of i++ && i–

1
2
3
4
var num1 = 2;
var num2 = 20;
var num3 = --num1 + num2; //equals 21
var num4 = num1 + num2; //equals 21
1
2
3
4
var num1 = 2;
var num2 = 20;
var num3 = num1-- + num2; //equals 22
var num4 = num1 + num2; //equals 21

All four of these operators work on any values, meaning not just integers but strings, Booleans, floating-point values, and objects.The increment and decrement operators follow these rules regarding values:

  • When used on a string that is a valid representation of a number, convert to a number and apply the change. The variable is changed from a string to a number.
  • When used on a string that is not a valid number, the variable’s value is set to NaN (discussed in Chapter 4). The variable is changed from a string to a number.
  • When used on a Boolean value that is false, convert to 0 and apply the change. The variable is changed from a Boolean to a number.
  • When used on a Boolean value that is true, convert to 1 and apply the change. The variable is changed from a Boolean to a number.
  • When used on a floating-point value, apply the change by adding or subtracting 1.
  • When used on an object, call its valueOf() method to get a value to work with. Apply the other rules. If the result is NaN, then call toString()and apply the other rules again. The variable is changed from an object to a number.

The following example demonstrates some of these rules:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var s1 = “2”; 
var s2 = “z”;
var b = false;
var f = 1.1;
var o = {
valueOf: function() {
return -1;
}
};

s1++;//value becomes numeric 3
s2++; //value becomes NaN
b++; //value becomes numeric 1
f--; //value becomes 0.10000000000000009 (due to floating-point inaccuracies)
o--; //value becomes numeric -2
Unary Plus and Minus

When used on a numeric value, the unary minus simply negates the value (as in this example). When used on nonnumeric values, unary minus applies all of the same rules as unary plus and then negates the result, as shown here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var s1 = “01”; 
var s2 = “1.1”;
var s3 = “z”;
var b = false;
var f = 1.1;
var o = {
valueOf: function() {
return -1;
}
};

s1 = -s1; //value becomes numeric -1
s2 = -s2; //value becomes numeric -1.1
s3 = -s3; //value becomes NaN
b = -b; //value becomes numeric 0
f = -f; //change to -1.1
o = -o; //value becomes numeric NaN

The unary plus is same to unary minus.

Bitwise Operators
Boolean Operators

Logical NOT

  • If the operand is an object, false is returned.
  • If the operand is a nonempty string, false is returned.
  • If the operand is any number other than 0 (including Infinity), false is returned.
  • empty string, true
  • number 0, true
  • NaN undefined、null, true

The logical NOT operator can also be used to convert a value into its Boolean equivalent. By using two NOT operators in a row, you can effectively simulate the behavior of the Boolean() casting function. The first NOT returns a Boolean value no matter what operand it is given. The second NOT negates that Boolean value and so gives the true Boolean value of a variable. The end result is the same as using the Boolean() function on a value, as shown here:

1
2
3
4
5
alert(!!”blue”); 	//true 
alert(!!0); //false
alert(!!NaN); //false
alert(!!””); //false
alert(!!12345); //true
Multiplicative Operators

There are three multiplicative operators in ECMAScript: multiply, divide, and modulus. These operators work in a manner similar to their counterparts in languages such as Java, C, and Perl.

But they also include some automatic type conversions when dealing with nonnumeric values. If either of the operands for a multiplication operation isn’t a number, it is converted to a number behind the scenes using the Number() casting function. This means that an empty string is treated as 0, and the Boolean value of true is treated as 1.

Additive Operators

Add

  • If both operands are strings, the second string is concatenated to the first.
  • If only one operand is a string, the other operand is converted to a string and the result is the concatenation of the two strings.

Subtract

  • If either operand is a string, a Boolean, null, or undefined, it is converted to a number (using Number() behind the scenes) and the arithmetic is calculated using the previous rules. If that conversion results in NaN, then the result of the subtraction is NaN.

  • If either operand is an object, its valueOf() method is called to retrieve a numeric value to represent it. If that value is NaN, then the result of the subtraction is NaN. If the object doesn’t have valueOf() defined, then toString() is called and the resulting string is converted into a number.

Relational Operators

As with other operators in ECMAScript, there are some conversions and other oddities that happen when using different data types. They are as follows:

  • If the operands are numbers, perform a numeric comparison.
  • If the operands are strings, compare the character codes of each corresponding character in the string.
  • If one operand is a number, convert the other operand to a number and perform a numeric comparison.
  • If an operand is an object, call valueOf() and use its result to perform the comparison according to the previous rules. If valueOf() is not available, call toString() and use that value according to the previous rules.
  • If an operand is a Boolean, convert it to a number and perform the comparison.

The letter “a” can’t be meaningfully converted into a number, so it becomes NaN. As a rule, the result of any relational operation with NaN is false, which is interesting when considering the following:

1
2
var result1 = NaN < 3; 	//false 
var result2 = NaN >= 3; //false

In most comparisons, if a value is not less than another, it is always greater than or equal to it. When using NaN, however, both comparisons return false.

Equal and Not Equal

When performing conversions, the equal and not-equal operators follow these basic rules:

  • If an operand is a Boolean value, convert it into a numeric value before checking for equality. A value of false converts to 0, whereas a value of true converts to 1.
  • If one operand is a string and the other is a number, attempt to convert the string into a number before checking for equality.
  • If one of the operands is an object and the other is not, the valueOf() method is called on the object to retrieve a primitive value to compare according to the previous rules.

The operators also follow these rules when making comparisons:

  • Values of null and undefined are equal.
  • If either operand is NaN, the equal operator returns false and the not-equal operator returns true. Important note: even if both operands are NaN, the equal operator returns false because, by rule, NaN is not equal to NaN.
  • If both operands are objects, then they are compared to see if they are the same object. If both operands point to the same object, then the equal operator returns true. Otherwise, the two are not equal.
compound assignment

Compound-assignment operators exist for each of the major mathematical operations and a few others as well. They are as follows:

  • Multiply/assign (*=)
  • Divide/assign (/=)
  • Modulus/assign (%=)
  • Add/assign (+=)
  • Subtract/assign (-=)
  • Left shift/assign (<<=)
  • Signed right shift/assign (>>=)
  • Unsigned right shift/assign (>>>=)

These operators are designed specifically as shorthand ways of achieving operations. They do not represent any performance improvement.

Comma Operator

Most often, the comma operator is used in the declaration of variables; however, it can also be used to assign values. When used in this way, the comma operator always returns the last item in the expression, as in the following example:

1
var num = (5, 1, 4, 8, 0); 	//num becomes 0

In this example, num is assigned the value of 0 because it is the last item in the expression.

STATEMENTS

The for Statement

This for loop is the same as the following:

1
2
3
4
5
var count = 10;
var i;
for (i=0; i < count; i++){
alert(i);
}

This code has the same affect as having the declaration of the variable inside the loop initialization. There are no block-level variables in ECMAScript , so a variable defined inside the loop is accessible outside the loop as well.

The initialization, control expression, and postloop expression are all optional. You can create an infinite loop by omitting all three, like this:

1
2
3
for (;;) {
doSomething();
}

Including only the control expression effectively turns a for loop into a while loop, as shown here:

1
2
3
4
5
6
var count = 10;
var i = 0;
for (; i < count; ){
alert(i);
i++;
}

This versatility makes the for statement one of the most used in the language.

The for-in Statement

The for-in statement is a strict iterative statement. It is used to enumerate the properties of an object. Here’s an example:

1
2
3
for (var propName in window) {
document.write(propName);
}

Here, the for-in statement is used to display all the properties of the BOM window object. Each time through the loop, the propName variable is filled with the name of a property that exists on the window object. This continues until all of the available properties have been enumerated over. As with the for statement, the var operator in the control statement is not necessary but is recommended for ensuring the use of a local variable.

Object properties in ECMAScript are unordered, so the order in which property names are returned in a for-in statement cannot necessarily be predicted. All enumerable properties will be returned once, but the order may differ across browsers.

Labeled Statements

It is possible to label statements for later use with the following example:

1
2
3
start: for (var i=0; i < count; i++) {
alert(i);
}

In this example, the label start can be referenced later by using the break or continue statement.

1
2
3
4
5
6
7
8
9
10
11
12
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost;
}
num++;
}
}

alert(num); //55
The with Statement
1
2
3
var qs = location.search.substring(1); 
var hostName = location.hostname;
var url = location.href;

use with

1
2
3
4
5
with(location){
var qs = search.substring(1);
var hostName = hostname;
var url = href;
}

In strict mode, the with statement is not allowed and is considered a syntax error.

It is widely considered a poor practice to use the with statement in production code because of its negative performance impact and the difficulty in debugging code contained in the with statement.

The switch Statement

Although the switch statement was borrowed from other languages, it has some unique characteristics in ECMAScript.

First, the switch statement works with all data types (in many languages it works only with numbers), so it can be used with strings and even with objects. Second, the case values need not be constants; they can be variables and even expressions. Consider the following example:

1
2
3
4
5
6
7
8
9
switch (“hello world”) {
case “hello” + “ world”:
alert(“Greeting was found.”);
break;
case “goodbye”:
alert(“Closing was found.”);
break;
default:
alert(“Unexpected message was found.”);

The ability to have case expressions also allows you to do things like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var num = 25; 
switch (true) {
case num < 0:
alert(“Less than 0.”);
break;
case num >= 0 && num <= 10:
alert(“Between 0 and 10.”);
break;
case num > 10 && num <= 20:
alert(“Between 10 and 20.”);
break;
default:
alert(“More than 20.”);
}

The switch statement compares values using the identically equal operator, so no type coercion occurs (for example, the string “10” is not equal to the number 10).

FUNCTIONS

Functions allow the encapsulation of statements that can be run anywhere and at any time.

1
2
3
function functionName(arg0, arg1,...,argN) {
statements
}

Keep in mind that a function stops executing and exits immediately when it encounters the return statement. Therefore, any code that comes after a return statement will never be executed. For example:

1
2
3
4
function sum(num1, num2) {
return num1 + num2;
alert(“Hello world”); //never executed
}

In this example, the alert will never be displayed because it appears after the return statement.

It’s also possible to have more than one return statement in a function, like this:

1
2
3
4
5
6
7
8
function diff(num1, num2) {
if (num1 < num2) {
return num2 - num1;
}
else {
return num1 - num2;
}
}

The return statement can also be used without specifying a return value. When used in this way, the function stops executing immediately and returns undefined as its value. This is typically used in functions that don’t return a value to stop function execution early, as in the following example, where the alert won’t be displayed:

1
2
3
4
function sayHi(name, message) {
return;
alert(“Hello “ + name + “, “ + message); //never called
}

Understanding Arguments

Functions in ECMAScript need not specify whether they return a value. Any function can return a value at any time by using the return statement followed by the value to return. Consider this example:

1
2
3
function sum(num1, num2) {
return num1 + num2;
}

Function arguments in ECMAScript don’t behave in the same way as function arguments in most other languages. An ECMAScript function doesn’t care how many arguments are passed in, nor does it care about the data types of those arguments.

The arguments object acts like an array (though it isn’t an instance of Array) in that you can access each argument using bracket notation (the fi rst argument is arguments[0], the second is arguments[1], and so on) and determine how many arguments were passed in by using the length property. In the previous example, the sayHi() function’s fi rst argument is named name. The same value can be accessed by referencing arguments[0]. Therefore, the function can be rewritten without naming the arguments explicitly, like this:

1
2
3
4
5
function sayHi() {
console,log('Hello ' + arguments[0] + ', + arguments[1]);
}

sayHi('rayjune', 'liga'); //Hello rayjune, liga

In this rewritten version of the function, there are no named arguments. The name and message arguments have been removed, yet the function will behave appropriately. This illustrates an important point about functions in ECMAScript: named arguments are a convenience, not a necessity.

The arguments object can also be used to check the number of arguments passed into the function via the length property. The following example outputs the number of arguments passed into the function each time it is called:

1
2
3
4
5
6
7
function howManyArgs() {
alert(arguments.length);
}

howManyArgs(“string”, 45); //2
howManyArgs(); //0
howManyArgs(12); //1

This example shows alerts displaying 2, 0, and 1 (in that order). In this way, developers have the freedom to let functions accept any number of arguments and behave appropriately. Consider the following:

1
2
3
4
5
6
7
8
9
10
11
function doAdd() {
if(arguments.length == 1) {
alert(arguments[0] + 10);
}
else if (arguments.length == 2) {
alert(arguments[0] + arguments[1]);
}
}

doAdd(10); //20
doAdd(30, 20); //50

The function doAdd() adds 10 to a number only if there is one argument; if there are two arguments, they are simply added together and returned. So doAdd(10) returns 20, whereas doAdd(30,20) returns 50. It’s not quite as good as overloading, but it is a sufficient workaround for this ECMAScript limitation.

Another important thing to understand about arguments is that the arguments object can be used in conjunction with named arguments, such as the following:

1
2
3
4
5
6
7
8
function doAdd(num1, num2) {
if(arguments.length == 1) {
alert(num1 + 10);
}
else if (arguments.length == 2) {
alert(arguments[0] + num2);
}
}

Another interesting behavior of arguments is that its values always stay in sync with the values of the corresponding named parameters. For example:

1
2
3
4
function doAdd(num1, num2) {
arguments[1] = 10;
alert(arguments[0] + num2);
}

Strict mode makes several changes to how the arguments object can be used. First, assignment, as in the previous example, no longer works. The value of num2 remains undefined even though arguments[1] has been assigned to 10. Second, trying to overwrite the value of arguments is a syntax error. (The code will not execute.)

All arguments in ECMAScript are passed by value. It is not possible to pass arguments by reference.

No Overloading

ECMAScript functions cannot be overloaded in the traditional sense. In other languages, such as Java, it is possible to write two defi nitions of a function so long as their signatures (the type and number of arguments accepted) are different. As just covered, functions in ECMAScript don’t have signatures, because the arguments are represented as an array containing zero or more values. Without function signatures, true overloading is not possible.

Variables, Scope, and Memory

Being loosely typed, a variable is literally just a name for a particular value at a particular time. Because there are no rules defi ning the type of data that a variable must hold, a variable’s value and data type can change during the lifetime of a script.

PRIMITIVE AND REFERENCE VALUES

ECMAScript variables may contain two different types of data: primitive values and reference values. Primitive values are simple atomic pieces of data, while reference values are objects that may be made up of multiple values.

When a value is assigned to a variable, the JavaScript engine must determine if it’s a primitive or a reference.

The five primitive types were discussed in the previous chapter: Undefined, Null, Boolean, Number, and String. These variables are said to be accessed by value, because you are manipulating the actual value stored in the variable.

Reference values are objects stored in memory. Unlike other languages, JavaScript does not permit direct access of memory locations, so direct manipulation of the object’s memory space is not allowed. When you manipulate an object, you’re really working on a reference to that object rather than the actual object itself. For this reason, such values are said to be accessed by reference.

In many languages, strings are represented by objects and are therefore considered to be reference types. ECMAScript breaks away from this tradition.

Dynamic Properties

Primitive and reference values are defined similarly: a variable is created and assigned a value. What you can do with those values once they’re stored in a variable, however, is quite different. When you work with reference values, you can add, change, or delete properties and methods at any time. Consider this example:

1
2
3
var person = new Object(); 
person.name = “Nicholas”;
alert(person.name); //”Nicholas”

Copying Values

Just consider this example:

1
2
3
4
5
6
var a = 123;
b = a; //123
b //123
b = 456;
b //456
a //123

When a primitive value is assigned from one variable to another, the value stored on the variable object is created and copied into the location for the new variable.

When a reference value is assigned from one variable to another, the value stored on the variable object is also copied into the location for the new variable. The difference is that this value is actually a pointer to an object stored on the heap. Once the operation is complete, two variables point to exactly the same object, so changes to one are reflected on the other, as in the following example:

1
2
3
4
5
6
7
8
var a = {a:1};
a //{a: 1}
var b = a;
b //{a: 1}
b.b = 2;
b.b //2
b //{a: 1, b: 2}
a //{a: 1, b: 2}

Argument Passing

All function arguments in ECMAScript are passed by value.

For primitive value:

1
2
3
4
5
6
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count); alert(count); //20,没有变化 alert(result); //30

For reference value:

1
2
3
4
5
6
7
8
9
10
11
12
13
function setName(obj) {
obj.name = "rayjune";
}
var person = new Object();
setName(person);
console.log(person.name); //"rayjune"
var person2 = new Object();
setName(person2);
console.log(person2.name); //"liga"

person2.name = 'someone';
console.log(person2.name); //someone
console.log(person.name); //rayjune

Think of function arguments in ECMAScript as nothing more than local variables.

Determining Type

“typeof” is the best way to determine if a variable is a string, number, Boolean, or undefined. If the value is an object or null, then typeof returns “object”, as in this example:

1
2
3
4
5
6
7
8
9
10
11
12
var s = “Nicholas”; 
var b = true;
var i = 22;
var u;
var n = null;
var o = new Object();
alert(typeof s); //string
alert(typeof i); //number
alert(typeof b); //boolean
alert(typeof u); //undefined
alert(typeof n); //object
alert(typeof o); //object

Although typeof works well for primitive values, it’s of little use for reference values. Typically, you don’t care that a value is an object — what you really want to know is what type of object it is.

To aid in this identification, ECMAScript provides the instanceof operator, which is used with the following syntax:

1
result = variable instanceof constructor

The instanceof operator returns true if the variable is an instance of the given reference type (identified by its prototype chain), Consider this example:

1
2
3
alert(person instanceof Object); 	//is the variable person an Object? 
alert(colors instanceof Array); //is the variable colors an Array?
alert(pattern instanceof RegExp); //is the variable pattern a RegExp?

All reference values, by definition, are instances of Object, so the instanceof operator always returns true when used with a reference value and the Object constructor. Similarly, if instanceof is used with a primitive value, it will always return false, because primitives aren’t objects.

typeof function will return function, not object.

EXECUTION CONTEXT AND SCOPE

Identifiers are resolved by navigating the scope chain in search of the identifier name. The search always begins at the front of the chain and proceeds to the back until the identifier is found. (If the identifier isn’t found, typically an error occurs.)

1
2
3
4
5
6
7
8
9
10
11
var color = 'blue';
function changeColor(){
if (color === 'blue'){
color = 'red';
}
else {
color = 'blue';
}
}
changeColor();
console.log(color); //red
Scope Chain Augmentation

Even though there are only two primary types of execution contexts, global and function (the third exists inside of a call to eval()), there are other ways to augment the scope chain. Certain statements cause a temporary addition to the front of the scope chain that is later removed after code execution. There are two times when this occurs, specifically when execution enters either of the following:

  • The catch block in a try-catch statement
  • A with statement

Both of these statements add a variable object to the front of the scope chain. For the with statement, the specified object is added to the scope chain; for the catch statement, a new variable object is created and contains a declaration for the thrown error object. Consider the following:

No Block-Level Scopes

This is important to keep in mind when dealing with the for statement, which is typically written like this:

1
2
3
4
for (var i=0; i < 10; i++){
doSomething(i);
}
alert(i); //10

Variable Declaration

1
2
3
4
5
6
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //causes an error since sum is not a valid variable

But without var

1
2
3
4
5
6
function add(num1, num2) {
sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //30

Initializing variables without declaring them is a very common mistake in JavaScript programming and can lead to errors. It’s advisable to always declare variables before initializing them to avoid such issues. In strict mode, initializing variables without declaration causes an error.

GARBAGE COLLECTION

The basic idea is simple: figure out which variables aren’t going to be used and free the memory associated with them. This process is periodic, with the garbage collector running at specified intervals (or at predefi ned collection moments in code execution).

Mark-and-Sweep

The most popular form of garbage collection for JavaScript is called mark-and-sweep. When a variable comes into context, such as when a variable is declared inside a function, it is flagged as being in context. Variables that are in context, logically, should never have their memory freed, because they may be used as long as execution continues in that context. When a variable goes out of context, it is also flagged as being out of context.

When the garbage collector runs, it marks all variables stored in memory (once again, in any number of ways). It then clears its mark off of variables that are in context and variables that are referenced by in-context variables. The variables that are marked after that are considered ready for deletion, because they can’t be reached by any in-context variables. The garbage collector then does a memory sweep, destroying each of the marked values and reclaiming the memory associated with them.

Reference Counting

Reference counting was initially used by Netscape Navigator 3.0 and was immediately met with a serious issue: circular references. A circular reference occurs when object A has a pointer to object B and object B has a reference to object A, such as in the following example:

1
2
3
4
5
6
7
function problem(){
var objectA = new Object();
var objectB = new Object();

objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}

In this example, objectA and objectB reference each other through their properties, meaning that each has a reference count of two. In a mark-and-sweep system, this wouldn’t be a problem because both objects go out of scope after the function has completed. In a reference-counting system, though, objectA and objectB will continue to exist after the function has exited, because their reference counts will never reach zero. If this function were called repeatedly, it would lead to a large amount of memory never being reclaimed. For this reason, Netscape abandoned a reference- counting garbage-collection routine in favor of a mark-and-sweep implementation in version 4.0. Unfortunately, that’s not where the reference-counting problem ended.

Performance

The garbage collector runs periodically and can potentially be an expensive process if there are a large number of variable allocations in memory, so the timing of the garbage-collection process is important.

Managing Memory

In a garbage-collected programming environment, developers typically don’t have to worry about memory management. However, JavaScript runs in an environment where memory management and garbage collection operate uniquely.

The amount of memory available for use in web browsers is typically much less than is available for desktop applications. This is more of a security feature than anything else, ensuring that a web page running JavaScript can’t crash the operating system by using up all the system memory. The memory limits affect not only variable allocation but also the call stack and the number of statements that can be executed in a single thread.

So keeping the amount of used memory to a minimum leads to better page performance. The best way to optimize memory usage is to ensure that you’re keeping around only data that is necessary for the execution of your code. When data is no longer necessary, it’s best to set the value to null, freeing up the reference — this is called dereferencing the value. This advice applies mostly to global values and properties of global objects.Local variables are dereferenced automatically when they go out of context, as in this example:

1
2
3
4
5
6
7
8
9
10
11
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}

var globalPerson = createPerson(“Nicholas”);

//do something with globalPerson

globalPerson = null;

SUMMARY

Two types of values can be stored in JavaScript variables: primitive values and reference values. Primitive values have one of the five primitive data types: Undefi ned, Null, Boolean, Number, and String. Primitive and reference values have the following characteristics:

  • Primitive values are of a fixed size and so are stored in memory on the stack.
  • Copying primitive values from one variable to another creates a second copy of the value.
  • Reference values are objects and are stored in memory on the heap
  • A variable containing a reference value actually contains just a pointer to the object, not the object itself.
  • Copying a reference value to another variable copies just the pointer, so both variables end up referencing the same object.
  • The typeof operator determines a value’s primitive type, whereas the instanceof operator is used to determine the reference type of a value.

All variables, primitive and reference, exist within an execution context (also called a scope) that determines the lifetime of the variable and which parts of the code can access it. Execution context can be summarized as follows:

  • Execution contexts exist globally (called the global context) and within functions.
  • Each time a new execution context is entered, it creates a scope chain to search for variables and functions.
  • Contexts that are local to a function have access not only to variables in that scope but also to variables in any containing contexts and the global context.
  • The global context has access only to variables and functions in the global context and cannot directly access any data inside local contexts.
  • The execution context of variables helps to determine when memory will be freed.

JavaScript is a garbage-collected programming environment where the developer need not be concerned with memory allocation or reclamation. JavaScript’s garbage-collection routine can be summarized as follows:

  • Values that go out of scope will automatically be marked for reclamation and will be deleted during the garbage-collection process.
  • The predominant garbage-collection algorithm is called mark-and-sweep, which marks values that aren’t currently being used and then goes back to reclaim that memory.
  • Another algorithm is reference counting, which keeps track of how many references there are to a particular value. JavaScript engines no longer use this algorithm, but it still affects Internet Explorer because of nonnative JavaScript objects (such as DOM elements) being accessed in JavaScript.
  • Reference counting causes problems when circular references exist in code.
  • Dereferencing variables helps not only with circular references but also with garbage collec- tion in general. To aid in memory reclamation, global objects, properties on global objects, and circular references should all be dereferenced when no longer needed.

Reference Types

A reference value (object) is an instance of a specific reference type. In ECMAScript, reference types are structures used to group data and functionality together and are often incorrectly called classes. Although technically an object-oriented language, ECMAScript lacks some basic constructs that have traditionally been associated with object-oriented programming, including classes and interfaces. Reference types are also sometimes called object definitions, because they describe the properties and methods that objects should have.

Even though reference types are similar to classes, the two concepts are not equivalent. To avoid any confusion, the term class is not used in the rest of this book.

Again, objects are considered to be instances of a particular reference type. New objects are created by using the new operator followed by a constructor. A constructor is simply a function whose purpose is to create a new object. Consider the following line of code:

1
var person = new Object();

This code creates a new instance of the Object reference type and stores it in the variable person. The constructor being used is Object(), which creates a simple object with only the default properties and methods. ECMAScript provides a number of native reference types, such as Object, to help developers with common computing tasks.

THE OBJECT TYPE

Up to this point, most of the reference-value examples have used the Object type, which is one of the most often-used types in ECMAScript. Although instances of Object don’t have much functionality, they are ideally suited to storing and transmitting data around an application.

There are two ways to explicitly create an instance of Object. The first is to use the new operator with the Object constructor like this:

1
2
3
var person = new Object(); 
person.name = 'rayjune';
person.age = 21;

The other way is to use object literal notation. Object literal notation is a shorthand form of object defi nition designed to simplify creating an object with numerous properties. For example, the following defi nes the same person object from the previous example using object literal notation:

1
2
3
4
var person = {
name : 'rayjune',
age : 21
};

Property names can also be specified as strings or numbers when using object literal notation, such as in this example:

1
2
3
4
var person = {
'name' : 'rayjune',
'age' : 21
};

It’s also possible to create an object with only the default properties and methods using object literal notation by leaving the space between the curly braces empty, such as this:

1
2
3
var person = {}; 	//same as new Object()
person.name = “Nicholas”;
person.age = 29;

This example is equivalent to the fi rst one in this section, though it looks a little strange. It’s recommended to use object literal notation only when you’re going to specify properties for readability.

When defi ning an object via object literal notation, the Object constructor is never actually called (Firefox 2 and earlier did call the Object constructor; this was changed in Firefox 3).

Though it’s acceptable to use either method of creating Object instances, developerstend to favor object literal notation, because it requires less code and visually encapsulates all related data. In fact, object literals have become a preferred way of passing a large number of optional arguments to a function, such as in this example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function displayInfo(args) {
var output = “”;
if (typeof args.name == “string”){
output += “Name: “ + args.name + “\n”;
}
if (typeof args.age == “number”) {
output += “Age: “ + args.age + “\n”;
}
alert(output);
}
displayInfo({
name: “Nicholas”,
age: 29
});
displayInfo({
name: “Greg”
});

( This pattern for argument passing is best used when there is a large number of optional arguments that can be passed into the function. Generally speaking, named arguments are easier to work with but can get unwieldy when there are numerous optional arguments. The best approach is to use named arguments for those that are required and an object literal to encompass multiple optional arguments. )

Generally speaking, dot notation is preferred unless variables are necessary to access properties by name.

1
person.name; //rayjune

THE ARRAY TYPE

An ECMAScript array is very different from arrays in most other programming languages. As in other languages, ECMAScript arrays are ordered lists of data, but unlike in other languages, they can hold any type of data in each slot. This means that it’s possible to create an array that has a string in the fi rst position, a number in the second, an object in the third, and so on. ECMAScript arrays are also dynamically sized, automatically growing to accommodate any data that is added to them.

Arrays can be created in two basic ways. The first is to use the Array constructor, as in this line:

1
2
3
4
5
6
7
8
9
10
11
var colors = new Array();
var colors = new Array(20);

var colors = new Array(“red”, “blue”, “green”);

var colors = new Array(3); //create an array with three items

var names = new Array(“Greg”); //create an array with one item, the string “Greg”

var colors = Array(3); //without new is also okay
var names = Array(“Greg”); //too

The second way to create an array is by using array literal notation. As in this example:

1
2
3
var colors = [“red”, “blue”, “green”]; //creates an array with three strings
var names = []; //creates an empty array
var values = [1,2,]; //AVOID! Creates an array with 2 or 3 items var options = [,,,,,]; //AVOID! creates an array with 5 or 6 items

As with objects, the Array constructor isn’t called when an array is created using array literal notation (except in Firefox prior to version 3).

A unique characteristic of length is that it’s not read-only. By setting the length property, you can easily remove items from or add items to the end of the array. Consider this example:

1
2
3
var colors = [“red”, “blue”, “green”];  ////creates an array with three strings
colors.length = 2;
alert(colors[2]); //undefined

If the length were set to a number greater than the number of items in the array, the new items would each get filled with the value of undefined, such as in this example:

1
2
3
var colors = [“red”, “blue”, “green”];  //creates an array with three strings
colors.length = 4;
alert(colors[3]); //undefined

The length property can also be helpful in adding items to the end of an array, as in this example:

1
2
3
var colors = [“red”, “blue”, “green”];  //creates an array with three strings
colors[colors.length] = “black”; //add a color (position 3)
colors[colors.length] = “brown”; //add another color (position 4)
1
2
3
var colors = [“red”, “blue”, “green”]; 
colors[99] = “black”;
alert(colors.length); //100

In this code, the colors array has a value inserted into position 99, resulting in a new length of 100 (99 + 1). Each of the other items, positions 3 through 98, doesn’t actually exist and soreturns undefined when accessed.

( Arrays can contain a maximum of 4,294,967,295 / 2^32 items, which should be plenty for almost all programming needs. If you try to add more than that number, an exception occurs. Trying to create an array with an initial size approaching this maximum may cause a long-running script error. )

Detecting Arrays

ECMAScript 5 introduced the Array.isArray() method. The purpose of this method is to definitively determine if a given value is an array regardless of the global execution context in which it was created. Example usage:

1
2
if (Array.isArray(value)){
//do something on the array }

Internet Explorer 9+, Firefox 4+, Safari 5+, Opera 10.5+, and Chrome have all implemented Array .isArray().

Conversion Methods

toString, valueOf, toLocalString, join

1
2
3
var a = [1,2,3];
a.toString(); //1,2,3 always ,
a.valueOf; //[1,2,3]
1
var colors = ["red", "green", "blue"]; alert(colors.join(",")); 	//red,green,blue alert(colors.join("||")); 	  //red||green||blue

If an item in the array is null or undefined, it is represented by an empty string in the result of join(), toLocaleString(), toString(), and valueOf().

Stack Methods

push & pop

1
2
3
4
5
6
7
var colors = new Array();
var count = colors.push(“red”, “green”); //create an array
alert(count); //2 //push two items

var item = colors.pop(); // black
alert(item);
alert(colors.length); //2

Queue Methods

Just as stacks restrict access in a LIFO data structure, queues restrict access in a first-in-fi rst-out (FIFO) data structure.

shift & push

1
2
3
4
5
6
7
8
9
10
var colors = new Array();	//create an array
var count = colors.push("red", "green"); //push two items
alert(count); //2

count = colors.push("black");
alert(count); //3

var item = colors.shift(); //"red"
alert(item);
alert(colors.length); //2

ECMAScript also provides an unshift() method for arrays. As the name indicates, unshift() does the opposite of shift()

1
2
3
var colors = new Array();	//create an array
var count = colors.unshift(“red”, “green”); //push two items at first
alert(count); //2

Reordering Methods

sort() & reverse()

By default, the sort() method puts the items in ascending order — with the smallest value first and the largest value last. To do this, the sort() method calls the String() casting function on every item and then compares the strings to determine the correct order. This occurs even if all items in an array are numbers, as in this example:

1
2
3
var values = [0, 1, 5, 10, 15]; 
values.sort();
alert(values); //0,1,10,15,5

Even though the values in this example begin in correct numeric order, the sort() method changes that order based on their string equivalents. So even though 5 is less than 10, the string “10” comes before “5” when doing a string comparison, so the array is updated accordingly. Clearly, this is not an optimal solution in many cases, so the sort() method allows you to pass in a comparison function that indicates which value should come before which.

A comparison function accepts two arguments and returns a negative number if the fi rst argument should come before the second, a zero if the arguments are equal, or a positive number if the fi rst argument should come after the second. Here’s an example of a simple comparison function:

1
2
3
4
5
6
7
8
9
10
11
function compare(value1, value2) {
if (value1 < value2) {
return -1;
}
else if (value1 > value2) {
return 1;
}
else {
return 0;
}
}

This comparison function works for most data types and can be used by passing it as an argument to the sort() method, as in the following example:

1
2
3
var values = [0, 1, 5, 10, 15]; 
values.sort(compare);
alert(values); //0,1,5,10,15

A much simpler version of the comparison function can be used with numeric types, and objects whose valueOf() method returns numeric values (such as the Date object). In either case, you can simply subtract the second value from the fi rst as shown here:

1
2
3
function compare(value1, value2){
return value2 - value1;
}

Manipulation Methods

concat()

The concat() method, for instance, allows you to create a new array based on all of the items in the current array.

1
2
3
4
var colors = [“red”, “green”, “blue”];
var colors2 = colors.concat(“yellow”, [“black”, “brown”]);
alert(colors); //red,green,blue
alert(colors2); //red,green,blue,yellow,black,brown
slice()

It creates an array that contains one or more items already contained in an new array. The slice() method may accept one or two arguments: the starting and stopping positions of the items to return. If only one argument is present, the method returns all items between that position and the end of the array. If there are two arguments, the method returns all items between the start position and the end position, not including the item in the end position. Keep in mind that this operation does not affect the original array in any way. Consider the following:

1
2
3
4
5
6
var colors = [“red”, “green”, “blue”, “yellow”, “purple”]; 
var colors2 = colors.slice(1);
var colors3 = colors.slice(1,4);

alert(colors2); //green,blue,yellow,purple
alert(colors3); //green,blue,yellow

If either the start or end position of slice() is a negative number, then the number is subtracted from the length of the array to determine the appropriate locations. For example, calling slice(-2, -1) on an array with fi ve items is the same as calling slice(3, 4). If the end position is smaller than the start, then an empty array is returned.

##### splice() the most powerful

The main purpose of splice() is to insert items into the middle of an array, but there are three distinct ways of using this method. They are as follows:

  • Deletion — Any number of items can be deleted from the array by specifying just two arguments: the position of the first item to delete and the number of items to delete. For example, splice(0, 2) deletes the first two items.
  • Insertion — Items can be inserted into a specific position by providing three or more arguments: the starting position, 0 (the number of items to delete), and the item to insert. Optionally, you can specify a fourth parameter, fifth parameter, or any number of other parameters to insert. For example, splice(2, 0, “red”, “green”) inserts the strings “red” and “green” into the array at position 2.
  • Replacement — Items can be inserted into a specific position while simultaneously deleting items, if you specify three arguments: the starting position, the number of items to delete, and any number of items to insert. The number of items to insert doesn’t have to match the number of items to delete. For example, splice(2, 1, “red”, “green”) deletes one item at position 2 and then inserts the strings “red” and “green” into the array at position 2.

The splice() method always returns an array that contains any items that were removed from the array (or an empty array if no items were removed). These three uses are illustrated in the following code:

1
2
3
4
var colors = [“red”, “green”, “blue”]; 
var removed = colors.splice(0,1); //remove the first item
alert(colors); //green,blue
alert(removed); //red - one item array
1
2
3
removed = colors.splice(1, 0, “yellow”, “orange”); //insert two items at position 1 
alert(colors); //green,yellow,orange,blue
alert(removed); //empty array
1
2
3
removed = colors.splice(1, 1, “red”, “purple”); //insert two values, remove one
alert(colors); //green,red,purple,orange,blue
alert(removed); //yellow - one item array
#### Location Methods

ECMAScript 5 adds two item location methods to array instances: indexOf() and lastIndexOf(). Each of these methods accepts two arguments: the item to look for and an optional index from which to start looking. The indexOf() method starts searching from the front of the array (item 0) and continues to the back, whereas lastIndexOf() starts from the last item in the array and continues to the front.

1
2
3
4
5
6
7
alert(numbers.lastIndexOf(4)); //5 
alert(numbers.indexOf(4, 4)); //5
alert(numbers.lastIndexOf(4, 4)); //3
var person = { name: "rayjune" };
var people = [{ name: "rayjune" }];
var morePeople = [person];
alert(people.indexOf(person)); //-1 alert(morePeople.indexOf(person)); //0

The indexOf() and lastIndexOf() methods make it trivial to locate specific items inside of an array .

Iterative Methods

ECMAScript 5 defines five iterative methods for arrays. Each of the methods accepts two arguments: a function to run on each item and an optional scope object in which to run the function (affecting the value of this).

The function passed into one of these methods will receive three arguments: the array item value, the position of the item in the array, and the array object itself. Depending on the method, the results of this function’s execution may or may not affect the method’s return value. The iterative methods are as follows:

  • every() — Runs the given function onevery item in the array and returns true if the function returns true for every item.
  • some() — Runs the given function on every item in the array and returns true if the function returns true for any one item.
  • filter() — Runs thegiven function on every item in the array and returns an array of all items for which the function returns true.
  • forEach() — Runs the given function on every item in the array. This method has no return value. Just run it, no return value.
  • map() — Runs the given function on every item in the array and returns the result of each function call in an array.

These methods do not change the values contained in the array.

Of these methods, the two most similar are every() and some(), which both query the array for items matching some criteria.

1
2
3
4
5
6
7
8
9
var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item, index, array){
return (item > 2);
});
alert(everyResult); //false
var someResult = numbers.some(function(item, index, array){
return (item > 2);
});
alert(someResult); //true

The next method is filter(), which uses the given function to determine if an item should be included in the array that it returns. For example, to return an array of all numbers greater than 2, the following code can be used:

1
2
3
4
5
var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item, index, array){
return (item > 2);
});
alert(filterResult); //[3,4,5,4,3]

Here, an array containing the values 3, 4, 5, 4, and 3 is created and returned by the call to filter(), because the passed-in function returns true for each of those items. This method is very helpful when querying an array for all items matching some criteria.

The map() method also returns an array. Each item in the array is the result of running the passed- in function on the original array item in the same location. For example, you can multiply every number in an array by two and are returned an array of those numbers, as shown here:

1
2
3
4
5
var numbers = [1,2,3,4,5,4,3,2,1];
var mapResult = numbers.map(function(item, index, array){
return item * 2;
});
alert(mapResult); //[2,4,6,8,10,8,6,4,2]

The last method is forEach(), which simply runs the given function on every item in an array. There is no return value and is essentially the same as iterating over an array using a for loop. Here’s an example:

1
2
3
4
var numbers = [1,2,3,4,5,4,3,2,1];
numbers.forEach(function(item, index, array){
//do something here
});

Reduction Methods

reduce() & reduceRight()

1
2
3
4
5
var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //15

THE DATE TYPE (temporary)

1
2
3
4
5
var now = new Date();

var someDate = new Date(Date.parse(“May 25, 2004”));

var someDate = new Date(“May 25, 2004”);

THE REGEXP TYPE (temporary)

THE FUNCTION TYPE

Functions actually are objects.

Each function is an instance of the Function type that has properties and methods just like any other reference type. Because functions are objects, function names are simply pointers to function objects and are not necessarily tied to the function itself. Functions are typically defined using function-declaration syntax, as in this example:

1
2
3
function sum (num1, num2) {
return num1 + num2;
}

This is almost exactly equivalent to using a function expression, such as this:

1
2
3
var sum = function(num1, num2){
return num1 + num2;
};

You should note that there is a semicolon after the end of the function, just as there would be after any variable initialization.

The last way to define functions is by using the Function constructor, which accepts any number of arguments. The last argument is always considered to be the function body, and the previous arguments enumerate the new function’s arguments. Take this for example:

1
var sum = new Function(“num1”, “num2”, “return num1 + num2”);	//not recommended

This syntax is not recommended because it causes a double interpretation of the code (once for the regular ECMAScript code and once for the strings that are passed into the constructor) and thus can affect performance. However, it’s important to think of functions as objects and function names as pointers — this syntax is great at representing that concept.

Because function names are simply pointers to functions, they act like any other variable containing a pointer to an object. This means it’s possible to have multiple names for a single function, as in this example:

1
2
3
4
5
6
7
8
9
10
function sum(num1, num2){
return num1 + num2;
}
alert(sum(10,10)); //20

var anotherSum = sum;
alert(anotherSum(10,10)); //20

sum = null;
alert(anotherSum(10,10)); //20

Note that using the function name without parentheses accesses the function pointer instead of executing the function. At this point, both anotherSum and sum point to the same function, meaning that anotherSum() can be called and a result returned.

When sum is set to null, it severs its relationship with the function, although anotherSum() can still be called without any problems.

No Overloading (Revisited)

Thinking of function names as pointers also explains why there can be no function overloading in ECMAScript. Recall the following example from Chapter 3:

1
2
3
4
5
6
7
function addSomeNumber(num){
return num + 100;
}
function addSomeNumber(num) {
return num + 200;
}
var result = addSomeNumber(100); //300

In this example, it’s clear that declaring two functions with the same name always results in the last function overwriting the previous one. This code is almost exactly equivalent to the following:

1
2
3
4
5
6
var addSomeNumber = function (num){
return num + 100;
};
addSomeNumber = function (num) {
return num + 200;
};

Function Declarations versus Function Expressions

Throughout this section, the function declaration and function expression are referred to as being almost equivalent. This hedging is due to one major difference in the way that a JavaScript engine loads data into the execution context. Function declarations are read and available in an execution context before any code is executed, whereas function expressions aren’t complete until the execution reaches that line of code. Consider the following:

1
2
3
4
alert(sum(10,10)); 
function sum(num1, num2){
return num1 + num2;
}

This code runs perfectly, because function declarations are read and added to the execution context before the code begins running through a process called function declaration hoisting. As the code is being evaluated, the JavaScript engine does a fi rst pass for function declarations and pulls them to the top of the source tree. So even though the function declaration appears after its usage in the actual source code, the engine changes this to hoist the function declarations to the top. Changing the function declaration to an equivalent function expression, as in the following example, will cause an error during execution:

1
2
3
4
alert(sum(10,10));
var sum = function(num1, num2){
return num1 + num2;
};

This updated code will cause an error, because the function is part of an initialization statement, not part of a function declaration. That means the function isn’t available in the variable sum until the highlighted line has been executed, which won’t happen, because the fi rst line causes an “unexpected identifier” error.

Aside from this difference in when the function is available by the given name, the two syntaxes are equivalent.

It is possible to have named function expressions that look like declarations, such as var sum = function sum() {}

Functions as Values

Because function names in ECMAScript are nothing more than variables, functions can be used any place any other value can be used.

This means it’s possible not only to pass a function into another function as an argument but also to return a function as the result of another function. Consider the following function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}

function add10(num){
return num + 10;
}

var result1 = callSomeFunction(add10, 10);
alert(result1); //20

function getGreeting(name){
return "Hello, " + name;
}

var result2 = callSomeFunction(getGreeting, "rayjune"); alert(result2); //"Hello, rayjune"

Function Internals

Two special objects exist inside a function: arguments and this.

arguments.callee

The arguments object, as discussed in Chapter 3, is an array-like object that contains all of the arguments that were passed into the function. Though its primary use is to represent function arguments, the arguments object also has a property named callee, which is a pointer to the function that owns the arguments object. Consider the following classic factorial function:

1
2
3
4
5
6
7
8
function factorial(num){
if (num <= 1) {
return 1;
}
else {
return num * factorial(num-1)
}
}

Factorial functions are typically defined to be recursive, as in this example, which works fine when the name of the function is set and won’t be changed.

However, the proper execution of this function is tightly coupled with the function name “factorial”. It can be decoupled by using arguments.callee as follows:

1
2
3
4
5
6
7
8
function factorial(num){
if (num <= 1) {
return 1;
}
else {
return num * this.arguments.callee(num-1);
}
}

In this rewritten version of the factorial() function, there is no longer a reference to the name “factorial” in the function body, which ensures that the recursive call will happen on the correct function no matter how the function is referenced. Consider the following:

1
2
3
4
5
6
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5)); //120
alert(factorial(5)); //0
this

The other special object is called this. It is a reference to the context object that the function is operating on — often called the this value (when a function is called in the global scope of a web page, the this object points to window). Consider the following:

1
2
3
4
5
6
7
8
window.color = “red”;
var o = { color: “blue” };
function sayColor(){
alert(this.color);
}
sayColor(); //”red”
o.sayColor = sayColor;
o.sayColor(); //”blue”

( Remember that function names are simply variables containing pointers, so the global sayColor() function and o.sayColor() point to the same function even though they execute in different contexts. )

Function Properties and Methods

Functions are objects in ECMAScript and, as mentioned previously, therefore have properties and methods. Each function has two properties: length and prototype.

length

The length property indicates the number of named arguments that the function expects, as in this example:

1
2
3
4
5
6
7
8
9
10
11
12
13
function sayName(name){
alert(name);
}
function sum(num1, num2){
return num1 + num2;
}
function sayHi(){
alert(“hi”);
}

alert(sayName.length); //1
alert(sum.length); //2
alert(sayHi.length); //0
prototype

The prototype is the actual location of all instance methods for reference types.

That meaning methods such as toString() and valueOf() actually exist on the prototype and are then accessed from the object instances. This property is very important in terms of defining your own reference types and inheritance. In ECMAScript 5, the prototype property is not enumerable and so will not be found using for-in.

There are two additional methods for functions: apply() and call(). These methods both call the function with a specific this value, effectively setting the value of the this object inside the function body.

apply()

The apply() method accepts two arguments: the value of this inside the function and an array of arguments. This second argument may be an instance of Array, but it can also be the arguments object. Consider the following:

1
2
3
4
5
6
7
8
9
10
11
12
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments); //passing in arguments object
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]); //passing in array
}

alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20

In this example, callSum1() executes the sum() method, passing in this as the this value (which is equal to window because it’s being called in the global scope) and also passing in the arguments object. The callSum2() method also calls sum(), but it passes in an array of the arguments instead. Both functions will execute and return the correct result.

In strict mode, the this value of a function called without a context object is not coerced to window. Instead, this becomes undefined unless explicitly set by either attaching the function to an object or using apply() or call().

call()

The call() method exhibits the same behavior as apply(), but arguments are passed to it differently:

  • The first argument is the this value,
  • but the remaining arguments are passed directly into the function.

Using call() arguments must be enumerated specifically, as in this example:

1
2
3
4
5
6
7
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //20

The decision to use either apply() or call() depends solely on the easiest way for you to pass arguments into the function. If you intend to pass in the arguments object directly or if you already have an array of data to pass in, then apply() is the better choice; otherwise, call() may be a more appropriate choice. (If there are no arguments to pass in, these methods are identical.)

true power of apply & call

The true power of apply() and call() lies not in their ability to pass arguments but rather in their ability to augment the this value inside of the function. Consider the following example:

1
2
3
4
5
6
7
8
9
10
window.color = “red”;
var o = { color: “blue” };
function sayColor(){
alert(this.color);
}

sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue

You can then call the function explicitly in the global scope by using sayColor.call(this) and sayColor.call(window), which both display “red”. Running sayColor.call(o) switches the context of the function such that this points to o, resulting in a display of “blue”.

The advantage of using call() (or apply()) to augment the scope is that the object doesn’t need to know anything about the method. In the first version of this example, the sayColor() function was placed directly on the object o before it was called; in the updated example, that step is no longer necessary.

bind()

ECMAScript 5 defi nes an additional method called bind(). The bind() method creates a new function instance whose this value is bound to the value that was passed into bind(). For example:

1
2
3
4
5
6
7
8
window.color = “red”;
var o = { color: “blue” };

function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue

The advantages of this technique are discussed in Chapter 22.

PRIMITIVE WRAPPER TYPES

Three special reference types are designed to ease interaction with primitive values: the Boolean type, the Number type, and the String type. These types can act like the other reference types described in this chapter, but they also have a special behavior related to their primitive-type equivalents.

Every time a primitive value is read, an object of the corresponding primitive wrapper type is created behind the scenes, allowing access to any number of methods for manipulating the data. Consider the following example:

1
2
var s1 = “some text”;
var s2 = s1.substring(2);

In this code, s1 is a variable containing a string, which is a primitive value. On the next line, the substring() method is called on s1 and stored in s2. Primitive values aren’t objects, so logically they shouldn’t have methods, though this still works as you would expect.

In truth, there is a lot going on behind the scenes to allow this seamless operation. When s1 is accessed in the second line, it is being accessed in read mode, which is to say that its value is being read from memory. Any time a string value is accessed in read mode, the following three steps occur:

  1. Create an instance of the String type.
  2. Call the specified method on the instance.
  3. Destroy the instance.

You can think of these three steps as they’re used in the following three lines of ECMAScript code:

1
2
3
var s1 = new String(“some text”); 
var s2 = s1.substring(2);
s1 = null;

This behavior allows the primitive string value to act like an object. These same three steps are repeated for Boolean and numeric values using the Boolean and Number types, respectively.

The major difference between reference types and primitive wrapper types is the lifetime of the object. When you instantiate a reference type using the new operator, it stays in memory until it goes out of scope, whereas automatically created primitive wrapper objects exist for only one line of code before they are destroyed. This means that properties and methods cannot be added at runtime. Take this for example:

1
2
3
var s1 = "some text";
s1.color = "red";
alert(s1.color); //undefined

It is possible to create the primitive wrapper objects explicitly using the Boolean, Number, and String constructors. This should be done only when absolutely necessary, because it is often confusing for developers as to whether they are dealing with a primitive or reference value. Calling typeof on an instance of a primitive wrapper type returns “object”, and all primitive wrapper objects convert to the Boolean value true.

The Object constructor also acts as a factory method and is capable of returning an instance of a primitive wrapper based on the type of value passed into the constructor. For example:

1
2
var obj = new Object(“some text”); 
alert(obj instanceof String); //true

The Boolean Type

Tt is recommended to never use the latter.

The Number Type

Similar to the Boolean object, the Number object gives important functionality to numeric values but really should not be instantiated directly because of the same potential problems.

The String Type

The String type is the object representation for strings and is created using the String constructor as follows:

1
var stringObject = new String(“hello world”);

length:

1
2
var stringValue = “hello world”; 
alert(stringValue.length); //”11”

This example outputs “11”, the number of characters in “hello world”. Note that even if the string contains a double-byte character (as opposed to an ASCII character, which uses just one byte), each character is still counted as one.

The String type has a large number of methods to aid in the dissection and manipulation of strings in ECMAScript.

Character Methods

Two methods access specific characters in the string: charAt() and charCodeAt(). These methods each accept a single argument, which is the character’s zero-based position. The charAt() method simply returns the character in the given position as a single-character string. (There is no character type in ECMAScript.) For example:

1
2
var stringValue = “hello world”; 
alert(stringValue.charAt(1)); //'e'

The character in position 1 of “hello world” is “e”, so calling charAt(1) returns “e”. If you want the character’s character code instead of the actual character, then calling charCodeAt() is the appropriate choice, as in the following example:

1
var stringValue = “hello world”; alert(stringValue.charCodeAt(1));	//outputs “101”

This example outputs “101”, which is the character code for the lowercase “e” character.

ECMAScript 5 defines another way to access an individual character. Supporting browsers allow you to use bracket notation with a numeric index to access a specific character in the string, as in this example:

1
2
var stringValue = “hello world”; 
alert(stringValue[1]); //”e”
String-Manipulation Methods
concat()

The first of these methods is concat(), which is used to concatenate one or more strings to another, returning the concatenated string as the result. Consider the following example:

1
2
3
4
var stringValue = “hello “;
var result = stringValue.concat(“world”);
alert(result); //”hello world”
alert(stringValue); //”hello”

The concat() method accepts any number of arguments, so it can create a string from any number of other strings, as shown here:

1
2
3
4
var stringValue = “hello “;
var result = stringValue.concat(“world”, “!”);
alert(result); //”hello world!”
alert(stringValue); //”hello”

Although the concat() method is provided for string concatenation, the addition operator(+) is used more often and, in most cases, actually performs better than the concat() method even when concatenating multiple strings.

slice() & substr() & substring

ECMAScript provides three methods for creating string values from a substring: slice(), substr(), and substring(). All three methods return a substring of the string they act on, and all accept either one or two arguments. The first argument is the position where capture of the substring begins; the second argument, if used, indicates where the operation should stop.

Just as with the concat() method, slice(), substr(), and substring() do not alter the value of the string itself — they simply return a primitive string value as the result, leaving the original unchanged. Consider this example:

1
2
3
4
5
6
7
var stringValue = "hello world"; 
alert(stringValue.slice(3)); //"lo world"
alert(stringValue.substring(3)); //"lo world"
alert(stringValue.substr(3)); //"lo world"
alert(stringValue.slice(3, 7)); //"lo w"
alert(stringValue.substr(3, 7)); //"lo w"
alert(stringValue.substring(3,7)); //"lo worl"
String Location Methods

indexof & lastIndexof

1
var stringValue = "hello world"; alert(stringValue.indexOf("o"));		//4  alert(stringValue.lastIndexOf("o"));	//7

Using this second argument allows you to locate all instances of a substring by looping callings to indexOf() or lastIndexOf(), as in the following example:

1
2
3
4
5
6
7
8
9
var stringValue = “Lorem ipsum dolor sit amet, consectetur adipisicing elit”; 
var positions = new Array();
var pos = stringValue.indexOf(“e”);

while(pos > -1){
positions.push(pos);
pos = stringValue.indexOf(“e”, pos + 1);
}
alert(positions); //”3,24,32,35,52”
trim()

ECMAScript 5 introduces a trim() method on all strings. The trim() method creates a copy of the string, removes all leading and trailing white space, and then returns the result. For example:

1
2
3
var stringValue = “ hello world “;
var trimmedStringValue = stringValue.trim(); alert(stringValue); //” hello world
alert(trimmedStringValue); //”hello world”

And trimLeft() & trimRight()

String Case Methods
1
var stringValue = "hello world"; alert(stringValue.toLocaleUpperCase());	//"HELLO WORLD"  alert(stringValue.toUpperCase());			//"HELLO WORLD"  alert(stringValue.toLocaleLowerCase());	//"hello world"  alert(stringValue.toLowerCase());			//"hello world"
String Pattern-Matching Methods
match()

The String type has several methods designed to pattern-match within the string. The first of these methods is match() and is essentially the same as calling a RegExp object’s exec() method. The match() method accepts a single argument, which is either a regular-expression string or a RegExp object. Consider this example:

1
2
3
4
5
6
7
8
var text = 'cat, bat, sat, fat'; 
var pattern = /.at/;

//same as pattern.exec(text)
var matches = text.match(pattern);
alert(matches.index); //0
alert(matches[0]); //”cat”
alert(pattern.lastIndex); //0

Another method for finding patterns is search(). The only argument for this method is the same as the argument for match(): a regular expression specified by either a string or a RegExp object. The search() method returns the index of the first pattern occurrence in the string or –1 if it’s not found. search() always begins looking for the pattern at the beginning of the string. Consider this example:

1
2
3
var text = “cat, bat, sat, fat”; 
var pos = text.search(/at/);
alert(pos); //1
replace()

To simplify replacing substrings, ECMAScript provides the replace() method. This method accepts two arguments.

1
2
3
4
5
6
var text = “cat, bat, sat, fat”;
var result = text.replace(“at”, “ond”);
alert(result); //”cond, bat, sat, fat”

result = text.replace(/at/g, “ond”);
alert(result); //”cond, bond, sond, fond”

The second argument of replace() may also be a function. When there is a single match, the function gets passed three arguments: the string match, the position of the match within the string, and the whole string. When there are multiple capturing groups, each matched string is passed in as an argument, with the last two arguments being the position of the pattern match in the string and the original string. The function should return a string indicating what the match should be replaced with. Using a function as the second argument allows more granular control over replacement text, as in this example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function htmlEscape(text){
return text.replace(/[<>”&]/g, function(match, pos, originalText){
switch(match){
case “<”:
return “&lt;”;
case “>”:
return “&gt;”;
case “&”:
return “&amp;”;
case “\””:
return “&quot;”;
}
});
}

alert(htmlEscape(“<p class=\”greeting\”>Hello world!</p>”)); //”&lt;p class=&quot;greeting&quot;&gt;Hello world!&lt;/p&gt”;
split()

The last string method for dealing with patterns is split(), which separates the string into an array of substrings based on a separator. The separator may be a string or a RegExp object. (The string is not considered a regular expression for this method.) An optional second argument, the array limit, ensures that the returned array will be no larger than a certain size. Consider this example:

1
2
3
4
var colorText = "red,blue,green,yellow"; 
var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"]
var colors2 = colorText.split(",", 2); //["red", "blue"]
var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""]
The localeCompare() Method
1
var stringValue = "yellow"; alert(stringValue.localeCompare("brick"));		//1  alert(stringValue.localeCompare("yellow"));	 //0  alert(stringValue.localeCompare("zoo"));		  //-1

SINGLETON BUILT-IN OBJECTS

ECMA-262 defines a built-in object as “any object supplied by an ECMAScript implementation, independent of the host environment, which is present at the start of the execution of an ECMAScript program.”

This means the developer does not need to explicitly instantiate a built-in object; it is already instantiated. You have already learned about most of the built-in objects, such as Object, Array, and String. There are two singleton built-in objects defined by ECMA-262: Global and Math.

The Global Object

In truth, there is no such thing as a global variable or global function; all variables and functions defined globally become properties of the Global object. Functions covered earlier in this book, such as isNaN(), isFinite(), parseInt(), and parseFloat() are actually methods of the Global object. In addition to these, there are several other methods available on the Global object.

URI-Encoding Methods

encodeURI & encodeURIComponent

The main difference between the two methods is that encodeURI() does not encode special characters that are part of a URI, such as the colon, forward slash, question mark, and pound sign, whereas encodeURIComponent() encodes every nonstandard character it fi nds. Consider this example:

1
2
3
4
5
var uri = `http://www.wrox.com/illegal value.htm#start`;  
//”http://www.wrox.com/illegal%20value.htm#start”
alert(encodeURI(uri));

alert(encodeURIComponent(uri)); //”http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start”

Generally speaking, you’ll use encodeURIComponent() much more frequently than encodeURI(), because it’s more common to encode query string arguments separately from the base URI.

Here, the uri variable contains a string that is encoded using encodeURIComponent(). The first value output is the result of decodeURI(), which replaced only the %20 with a space. The second value is the output of decodeURIComponent(), which replaces all the special characters and outputs a string that has no escaping in it. (This string is not a valid URI.)

1
2
3
4
5
var uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start";
alert(decodeURI(uri)); //http%3A%2F%2Fwww.wrox.com%2Fillegal value.htm%23start

alert(decodeURIComponent(uri));
//http://www.wrox.com/illegal value.htm#start
The eval() Method

The final method is perhaps the most powerful in the entire ECMAScript language: the eval() method. This method works like an entire ECMAScript interpreter and accepts one argument, a string of ECMAScript (or JavaScript) to execute. Here’s an example:

1
eval(“alert(‘hi’)”);

This line is functionally equivalent to the following:

1
alert(“hi”);

Any variables or functions created inside of eval() will not be hoisted, as they are contained within a string when the code is being parsed. They are created only at the time of eval() execution.

1
2
3
4
5
6
7
8
var msg = "hello world";
eval("alert(msg)"); //"hello world"

eval("function sayHi() { alert('hi'); }");
sayHi(); //"hello world"

eval("var msg = 'hello world'; ");
alert(msg); //"hello world"

In strict mode, variables and functions created inside of eval() are not accessible outside, so these last two examples would cause errors. Also, in strict mode, assigning a value to eval causes an error:

1
2
“use strict”; 
eval = “hi”; //causes error

The capability to interpret strings of code is very powerful but also very dangerous. Use extreme caution with eval(), especially when passing user- entered data into it. A mischievous user could insert values that might compromise your site or application security. (This is called code injection.)

Global Object Properties
The Window Object

Though ECMA-262 doesn’t indicate a way to access the Global object directly, web browsers implement it such that the window is the Global object’s delegate. Therefore, all variables and functions declared in the global scope become properties on window. Consider this example:

1
2
3
4
5
var color = “red”;
function sayColor(){
alert(window.color);
}
window.sayColor(); //”red”

The window object does much more in JavaScript than just implement the ECMAScript Global object. Details of the window object and the Browser Object Model are discussed in Chapter 8.

The Math Object

The computations available on the Math object execute faster than if you were to write the computations in JavaScript directly. There are a number of properties and methods to help these computations.

The min() and max() Methods
1
2
3
4
5
var max = Math.max(3, 54, 32, 16); 
alert(max); //54

var min = Math.min(3, 54, 32, 16);
alert(min); //3

To find the maximum or the minimum value in an array, you can use the apply() method as follows:

1
2
var values = [1, 2, 3, 4, 5, 6, 7, 8]; 
var max = Math.max.apply(Math, values);
Rounding Methods
  • The Math.ceil() method represents the ceiling function, which always rounds numbers up to the nearest integer value.
  • The Math.floor() method represents the floor function, which always rounds numbers down to the nearest integer value.
  • The Math.round() method represents a standard round function, which rounds up if the number is at least halfway to the next integer value (0.5 or higher) and rounds down if not. This is the way you were taught to round in elementary school.
1
2
3
alert(Math.ceil(25.9));		//26 
alert(Math.ceil(25.5)); //26
alert(Math.ceil(25.1)); //26
The random() Method

The Math.random() method returns a random number between the 0 and the 1, not including either 0 or 1. This is a favorite tool of web sites that are trying to display random quotes or random facts upon entry of a web site. You can use Math.random() to select numbers within a certain integer range by using the following formula:

1
number = Math.floor(Math.random() * total_number_of_choices + first_possible_value)

You see 10 possible values (1 through 10), with the first possible value being 1. If you want to select a number between 2 and 10, then the code would look like this:

1
var num = Math.floor(Math.random() * 9 + 2);

There are only nine numbers when counting from 2 to 10, so the total number of choices is nine, with the fi rst possible value being 2. Many times, it’s just easier to use a function that handles the calculation of the total number of choices and the fi rst possible value, as in this example:

1
2
3
4
5
6
7
function selectFrom(lowerValue, upperValue) {
var choices = upperValue - lowerValue + 1;
return Math.floor(Math.random() * choices + lowerValue);
}

var num = selectFrom(2,10);
alert(num); //number between 2 and 10, inclusive

Using the function, it’s easy to select a random item from an array, as shown here:

1
2
var colors = [“red”, “green”, “blue”, “yellow”, “black”, “purple”, “brown”]; 
var color = colors[selectFrom(0, colors.length-1)];

In this example, the second argument to selectFrom() is the length of the array minus 1, which is the last position in an array.

Object-Oriented Programming

ECMA-262 defi nes an object as an “unordered collection of properties each of which contains a primitive value, object, or function.”

Strictly speaking, this means that an object is an array of values in ·no particular order·. Each property or method is identified by a name that is mapped to a value. For this reason (and others yet to be discussed), it helps to think of ECMAScript objects as hash tables: nothing more than a grouping of name-value pairs where the value may be data or a function.

Each object is ·created based on a reference type·, either one of the native types discussed in the previous chapter, or a developer-defined type.

UNDERSTANDING OBJECTS

1
2
3
4
5
6
7
8
var person = new Object(); 
person.name = “rayjune”;
person.age = 21;

person.job = “Software Engineer”;
person.sayName = function(){
alert(this.name);
};

Object literals became the preferred pattern for creating such objects. The previous example can be rewritten using object literal notation as follows:

1
2
3
4
5
6
7
var person = {
name: “rayjune”,
age: 21,
job: “Software Engineer”,
sayName: function(){
alert(this.name); }
}

The person object in this example is equivalent to the person object in the prior example, with all the same properties and methods. These properties are all created with certain characteristics that define their behavior in JavaScript.

Types of Properties

ECMA-262 fi fth edition describes characteristics of properties through the use of internal-only attributes. These attributes are defined by the specification for implementation in JavaScript engines, and as such, these attributes are not directly accessible in JavaScript. To indicate that an attribute is internal, surround the attribute name with two pairs of square brackets, such as [[Enumerable]].

There are two types of properties: data properties and accessor properties.

Data Properties

Data properties contain a single location for a data value. Values are read from and written to this location. Data properties have four attributes describing their behavior:

  • [[Configurable]] — Indicates if the property may be redefined by removing the property via delete,changing the property’s attributes, or changing the property into an accessor property. By default, this is true for all properties defined directly on an object, as in the previous example.
  • [[Enumerable]] — Indicates if the property will be returned in a for-in loop. By default, this is true for all properties defined directly on an object, as in the previous example.
  • [[Writable]] — Indicates if the property’s value can be changed. By default, this is true for all properties defi ned directly on an object, as in the previous example.
  • [[Value]] — Contains the actual data value for the property. This is the location from which the property’s value is read and the location to which new values are saved. The default value for this attribute is undefined.

When a property is explicitly added to an object as in the previous examples, [[Configurable]], [[Enumerable]], and [[Writable]] are all set to true while the [[Value]] attribute is set to the assigned value. For example:

1
2
3
var person = {
name: “rayjune”
};

Here, the property called name is created and a value of “rayjune” is assigned. That means [[Value]] is set to “rayjune”, and any changes to that value are stored in this location.

To change any of the default property attributes, you must use the ECMAScript 5 Object .defineProperty() method. This method accepts 3 arguments:

  1. the object on which the property should be added or modified
  2. the name of the property
  3. a descriptor object.

The properties on the descriptor object match the attribute names: configurable, enumerable, writable, and value. You can set one or all of these values to change the corresponding attribute values. For example:

1
2
3
4
5
6
7
8
9
var person = {}; 
Object.defineProperty(person, “name”, {
writable: false,
value: “rayjune”
});

alert(person.name); //rayjune
person.name = “liga”;
alert(person.name); //rayjune

Similar rules apply to creating a nonconfigurable property. For example:

1
2
3
4
5
6
7
8
9
var person = {}; 
Object.defineProperty(person, “name”, {
configurable: false,
value: “rayjune”
});

alert(person.name); //rayjune
delete person.name;
alert(person.name); //rayjune

Here, setting configurable to false means that the property cannot be removedfrom the object.

When you are using Object.defineProperty(), the values for configurable, enumerable, and writable default to false unless otherwise specified. In most cases, you likely won’t need the powerful options provided by Object.defineProperty(), but it’s important to understand the concepts to have a good understanding of JavaScript objects.

Accessor Properties

Accessor properties do not contain a data value. Instead, they contain a combination of a getter function and a setter function (though both are not necessary).

When an accessor property is read from, the getter function is called, and it’s the function’s responsibility to return a valid value; when an accessor property is written to, a function is called with the new value, and that function must decide how to react to the data. Accessor properties have four attributes:

  • [[Configurable]] — Indicates if the property may be redefined by removing the property via delete, changing the property’s attributes, or changing the property into a data property. By default, this is true for all properties defined directly on an object.
  • [[Enumerable]] — Indicates if the property will be returned in a for-in loop. By default, this is true for all properties defined directly on an object.
  • [[Get]] — The function to call when the property is read from. The default value is undefined.
  • [[Set]] — The function to call when the property is written to. The default value is undefined.

It is not possible to define an accessor property explicitly; you must use Object.defineProperty(). Here’s a simple example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var book = {
_year: 2004,
edition: 1
};

Object.defineProperty(book, 'year', {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});

book.year = 2005;
alert(book.edition); //2

In this code, an object book is created with two default properties: _year and edition. The underscore on _year is a common notation to indicate that a property is not intended to be accessed from outside of the object’s methods.

The year property is defined to be an accessor property where the getter function simply returns the value of _year and the setter does some calculation to determine the correct edition. So changing the year property to 2005 results in both _year and edition changing to 2. This is a typical use case for accessor properties, when setting a property value results in some other changes to occur.

Defining Multiple Properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});

This code defines two data properties, _year and edition, and an accessor property called year on the book object.

Reading Property Attributes

It’s also possible to retrieve the property descriptor for a given property by using the ECMAScript 5 Object.getOwnPropertyDescriptor() method. This method accepts two arguments: the object on which the property resides and the name of the property whose descriptor should be retrieved.

The return value is an object with properties for configurable, enumerable, get, and set for accessor properties or configurable, enumerable, writable, and value for data properties. Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});

var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
//2004
console.log(descriptor.value); console.log(descriptor.configurable); //false

var descriptor = Object.getOwnPropertyDescriptor(book, “year”);
//undefined
console.log(descriptor.value);
console.log(descriptor.enumerable); //false
console.log(typeof descriptor.get); //”function”

The Object.getOwnPropertyDescriptor() method can be used on any object in JavaScript, including DOM and BOM objects.

OBJECT CREATION

Although using the Object constructor or an object literal are convenient ways to create single objects, there is an obvious downside: creating multiple objects with the same interface requires a lot of code duplication. To solve this problem, developers began using a variation of the factory pattern.

The Factory Pattern

With no way to defi ne classes in ECMAScript, developers created functions to encapsulate the creation of objects with specific interfaces, such as in this example:

1
2
3
4
5
6
7
8
9
10
11
12
13
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
};
return o;
}

var person1 = createPerson('rayjune', 21, 'Software Engineer');
var person2 = createPerson('pogba', 27, 'footbool player');

Though this solved the problem of creating multiple similar objects, the factory pattern didn’t address the issue of object identification (what type of object an object is). As JavaScript continued to evolve, a new pattern emerged.

The Constructor Pattern

As mentioned in previous chapters, constructors in ECMAScript are used to create specific types of objects.

There are native constructors, such as Object and Array, which are available automatically in the execution environment at runtime. It is also possible to define custom constructors that define properties and methods for your own type of object. For instance, the previous example can be rewritten using the constructor pattern as the following:

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
};
}

var person1 = Person('rayjune', 21, 'Software Engineer');
var person2 = Person('pogba', 27, 'footbool player');

In this example, the Person() function takes the place of the factory createPerson() function. Note that the code inside Person() is the same as the code inside createPerson(), with the following exceptions:

  • There is no object being created explicitly.
  • The properties and method are assigned directly onto the this object.
  • There is no return statement.

Also note the name of the function is Person with an uppercase P. By convention, constructor functions always begin with an uppercase letter, whereas nonconstructor functions begin with a

To create a new instance of Person, use the new operator. Calling a constructor in this manner essentially causes the following four steps to be taken:

  1. Create a new object.
  2. Assign the this value of the constructor to the new object (so this points to the new object).
  3. Execute the code inside the constructor (adds properties to the new object).
  4. Return the new object.

At the end of the preceding example, person1 and person2 are each filled with a different instance of Person. Each of these objects has a constructor property that points back to Person, as follows:

1
console.log(person1.constructor == Person); console.log(person2.constructor == Person);

Defining your own constructors ensures that instances can be identified as a particular type later on, which is a great advantage over the factory pattern. In this example, person1 and person2 are considered to be instances of Object, because all custom objects inherit from Object .

Constructors as Functions

The only difference between constructor functions and other functions is the way in which they are called. Constructors are, after all, just functions; there’s no special syntax to define a constructor that automatically makes it behave as such.

Any function that is called with the new operator acts as a constructor, whereas any function called without it acts just as you would expect a normal function call to act. For instance, the Person() function from the previous example may be called in any of the following ways:

1
2
3
4
5
6
7
8
9
10
11
//use as a constructor
var person = new Person(“rayjune”, 21, “Software Engineer”); person.sayName(); //”rayjune”

//call as a function
Person(“pogba”, 27, “football player”); //adds to window
window.sayName(); //”pogba”

//call in the scope of another object
var o = new Object();
Person.call(o, “Kristen”, 25, “Nurse”);
o.sayName(); //”Kristen”

The Person() function can also be called within the scope of a particular object using call() (or apply()). In this case, it’s called with a this value of the object o, which then gets assigned all of the properties and the sayName() method.

Problems with Constructors

Though the constructor paradigm is useful, it is not without its faults. The major downside to constructors is that methods are created once for each instance. So, in the previous example, both person1 and person2 have a method called sayName(), but those methods are not the same instance of Function. Remember,functions are objects in ECMAScript, so every time a function is defined, it’s actually an object being instantiated. Logically, the constructor actually looks like this:

1
2
3
4
5
6
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function(“alert(this.name)”); //
}

It doesn’t make sense to have two instances of Function that do the same thing, especially when the this object makes it possible to avoid binding functions to particular objects until runtime.

It’s possible to work around this limitation by moving the function definition outside of the constructor, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}

function sayName(){
alert(this.name);
}

var person1 = new Person(“rayjune”, 21, “Software Engineer”); var person2 = new Person(“pogba”, 27, “football player”);

This solves the problem of having duplicate functions that do the same thing but also creates some clutter in the global scope by introducing a function that can realistically be used only in relation to an object. If the object needed multiple methods, that would mean multiple global functions, and all of a sudden the custom reference type definition is no longer nicely grouped in the code. These problems are addressed by using the prototype pattern.

The Prototype Pattern

Each function is created with a prototype property, which is an object containing properties and methods that should be available to instances of a particular reference type.

This object is literally a prototype for the object to be created once the constructor is called. The benefit of using the prototype is that all of its properties and methods are shared among object instances. Instead of assigning object information in the constructor, they can be assigned directly to the prototype, as in this example:

1
2
3
4
5
6
7
8
9
10
11
12
function Person(){ }

Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
person1.sayName(); //"Nicholas"

var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true

Unlike the constructor pattern, the properties and methods are all shared among instances, so person1 and person2 are both accessing the same set of properties and the same sayName() function.

To understand how this works, you must understand the nature of prototypes in ECMAScript.

How Prototypes Work

Whenever a function is created, its prototype property is also created according to a specific set of rules.

By default, all prototypes automatically get a property called constructor that points back to the function on which it is a property. In the previous example, for instance, Person.prototype.constructor points to Person. Then, depending on the constructor, other properties and methods may be added to the prototype.

When defining a custom constructor, the prototype gets the constructor property only by default; all other methods are inherited from Object. Each time the constructor is called to create a new instance, that instance has an internal pointer to the constructor’s prototype. In ECMA-262 fifth edition, this is called [[Prototype]]. There is no standard way to access [[Prototype]] from script, but Firefox, Safari, and Chrome all support a property on every object called proto.; in other implementations, this property is completely hidden from script.

The important thing to understand is that a direct link exists between the instance and the constructor’s prototype but not between the instance and the constructor.

Even though [[Prototype]] is not accessible in all implementations, the isPrototypeOf() method can be used to determine if this relationship exists between objects. Essentially, isPrototypeOf() returns true if [[Prototype]] points to the prototype on which the method is being called, as shown here:

1
(Person.prototype.isPrototypeOf(person1));	 alert(Person.prototype.isPrototypeOf(person2)); //two all 'true'

ECMAScript 5 adds a new method called Object.getPrototypeOf(), which returns the value of [[Prototype]] in all supporting implementations. For example:

1
alert(Object.getPrototypeOf(person1) == Person.prototype);  alert(Object.getPrototypeOf(person1).name); //”rayjune”`

Using Object.getPrototypeOf(), you are able to retrieve an object’s prototype easily, which becomes important once you want to implement inheritance using the prototype.

Whenever a property is accessed for reading on an object, a search is started to fi nd a property with that name. The search begins on the object instance itself. If a property with the given name is found on the instance, then that value is returned; if the property is not found, then the search continues up the pointer to the prototype, and the prototype is searched for a property with the same name. If the property is found on the prototype, then that value is returned.

The constructor property mentioned earlier exists only on the prototype and so is accessible from object instances.

Although it’s possible to read values on the prototype from object instances, it is not possible to overwrite them. If you add a property to an instance that has the same name as a property on the prototype, you create the property on the instance, which then masks the property on the prototype. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
function Person(){ }
Person.prototype.name = “rayjune”; Person.prototype.age = 21; Person.prototype.job = “Software Engineer”; Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = “Greg”;
alert(person1.name); //”Greg” - from instance
alert(person2.name); //”rayjune” - from prototype

The delete operator, however, completely removes the instance property and allows the prototype property to be accessed again as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(){ }

Person.prototype.name = “rayjune”;
Person.prototype.age = 21;
Person.prototype.job = “Software Engineer”; Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = “Greg”;

alert(person1.name); //”Greg” - from instance
alert(person2.name); //”rayjune” - from prototype

delete person1.name;
alert(person1.name); //'rayjune' - from prototype

The hasOwnProperty() method determines if a property exists on the instance or on the prototype. This method, which is inherited from Object, returns true only if a property of the given name exists on the object instance, as in this example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(){ }
Person.prototype.name = "rayjune";
Person.prototype.age = 21;
Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnProperty("name")); //false

person1.name = “Greg”;
//”Greg” - from instance
alert(person1.name);
alert(person1.hasOwnProperty(“name”)); //true
Prototypes and the in Operator

There are two ways to use the in operator: on its own or as a for-in loop.

When used on its own, the in operator returns true when a property of the given name is accessible by the object, which is to say that the property may exist on the instance or on the prototype. Consider the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(){ }

Person.prototype.name = “rayjune”;
Person.prototype.age = 21;

Person.prototype.job = “Software Engineer”; Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnProperty(“name”)); //false
alert(“name” in person1); //true

person1.name = “Greg”;
alert(person1.name);
alert(person1.hasOwnProperty(“name”)); //true
alert(“name” in person1); //true

It’s possible to determine if the property of an object exists on the prototype by combining a call to hasOwnProperty() with the in operator like this:

1
2
3
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
1
2
3
4
5
6
7
8
9
10
11
12
function Person(){ }
Person.prototype.name = "rayjune";
Person.prototype.age = 21;
Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){
alert(this.name);
};

var person = new Person();
alert(hasPrototypeProperty(person, "name")); //true

person.name = "Greg";
alert(hasPrototypeProperty(person, "name")); //false

When using a for-in loop, all properties that are accessible by the object and can be enumerated will be returned, which includes properties both on the instance and on the prototype.

To retrieve a list of all enumerable instance properties on an object, you can use the ECMAScript 5 Object.keys() method, which accepts an object as its argument and returns an array of strings containing the names of all enumerable properties. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(){ }
Person.prototype.name = "rayjune";
Person.prototype.age = 21;
Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){
alert(this.name);
};

var keys = Object.keys(Person.prototype);
alert(keys); //”name,age,job,sayName”

var p1 = new Person();
p1.name = “Rob”;
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //”name,age”

Here, the keys variable is filled with an array containing “name”, “age”, “job”, and “sayName”. This is the order in which they would normally appear using for-in. When called on an instance of Person, Object.keys() returns an array of name and age, the only two instance properties.

If you’d like a list of all instance properties, whether enumerable or not, you can use Object .getOwnPropertyNames() in the same way:

1
var keys = Object.getOwnPropertyNames(Person.prototype); alert(keys); //”constructor,name,age,job,sayName”

Note the inclusion of the non-enumerable constructor property in the list of results. Both Object .keys() and Object.getOwnPropertyNames() may be suitable replacements for using for-in.

Alternate Prototype Syntax

You may have noticed in the previous example that Person.prototype had to be typed out for each property and method. To limit this redundancy and to better visually encapsulate functionality on the prototype, it has become more common to simply overwrite the prototype with an object literal that contains all of the properties and methods, as in this example:

1
2
3
4
5
6
7
8
9
10
function Person(){ }

Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};

The end result is the same, with one exception: the constructor property no longer points to Person. When a function is created, its prototype object is created and the constructor is automatically assigned. Essentially, this syntax overwrites the default prototype object completely, meaning that the constructor property is equal to that of a completely new object (the Object constructor) instead of the function itself. Although the instanceof operator still works reliably, you cannot rely on the constructor to indicate the type of object, as this example shows:

1
2
3
4
5
var friend = new Person(); 
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //false

Here, instanceof still returns true for both Object and Person, but the constructor property is now equal to Object instead of Person. If the constructor’s value is important, you can set it specifically back to the appropriate value, as shown here:

1
2
3
4
5
6
7
8
9
10
function Person(){ }

Person.prototype = {
constructor: Person, name : “Nicholas”,
age : 29,
job : “Software Engineer”,
sayName : function () {
alert(this.name);
}
}

Keep in mind that restoring the constructor in this manner creates a property with [[Enumerable]] set to true. Native constructor properties are not enumerable by default.

Dynamic Nature of Prototypes

Since the process of looking up values on a prototype is a search, changes made to the prototype at any point are immediately reflected on instances, even the instances that existed before the change was made.

1
2
3
4
5
6
7
var friend= new Person();

Person.prototype.sayHi = function(){
alert(“hi”);
};

friend.sayHi(); //'hi' -works

Although properties and methods may be added to the prototype at any time, and they are reflected instantly by all object instances, you cannot overwrite the entire prototype and expect the same behavior.

The [[Prototype]] pointer is assigned when the constructor is called, so changing the prototype to a different object severs the tie between the constructor and the original prototype. Remember: the instance has a pointer to only the prototype, not to the constructor. Consider the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(){ }
var friend= new Person();

Person.prototype = {
constructor: Person,
name : "rayjune",
age : 21,
job : "Software Engineer", sayName : function () {
alert(this.name);
}
};

friend.sayName(); //error

Overwriting the prototype on the constructor means that new instances will reference the new prototype while any previously existing object instances still reference the old prototype.

Native Object Prototypes

The prototype pattern is important not just for defining custom types but also because it is the pattern used to implement all of the native reference types. Each of these (including Object, Array, String, and so on) has its methods defined on the constructor’s prototype. For instance, the sort() method can be found on Array.prototype, and substring() can be found on String.prototype, as shown here:

1
2
alert(typeof Array.prototype.sort); 		//”function” 
alert(typeof String.prototype.substring); //”function”

Through native object prototypes, it’s possible to get references to all of the default methods and to define new methods. Native object prototypes can be modified just like custom object prototypes, so methods can be added at any time. For example, the following code adds a method called startsWith() to the String primitive wrapper:

1
2
3
4
5
6
String.prototype.startsWith = function (text) {
return this.indexOf(text) == 0;
};

var msg = “Hello world!”;
alert(msg.startsWith(“Hello”)); //true

( Although possible, it is not recommended to modify native object prototypes in a production environment. This can often cause confusion and create possible name collisions if a method that didn’t exist natively in one browser is implemented natively in another. It’s also possible to accidentally overwrite native methods. )

Problems with Prototypes

The prototype pattern isn’t without its faults. For one, it negates the ability to pass initialization arguments into the constructor, meaning that all instances get the same property values by default. Although this is an inconvenience, it isn’t the biggest problem with prototypes. The main problem comes with their shared nature.

All properties on the prototype are shared among instances, which is ideal for functions. Properties that contain primitive values also tend to work well, as shown in the previous example, where it’s possible to hide the prototype property by assigning a property of the same name to the instance. The real problem occurs when a property contains a reference value. Consider the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person() {};
Person.prototype = {
constructor: Person,
name : “Nicholas”,
age : 29,
job : “Software Engineer”,
friends : [“Shelby”, “Court”],
sayName : function () {
alert(this.name);
}
};

var person1 = new Person();
var person2 = new Person();

person1.friends.push(“Van”);
alert(person1.friends); // 'Shelby,Court,Van'
alert(person2.friends); // 'Shelby,Court,Van'
alert(person1.friends === person2.friends); //true

Combination Constructor/Prototype Pattern

The most common way of defining custom types is to combine the constructor and prototype patterns.

The constructor pattern defines instance properties, whereas the prototype pattern defines methods and shared properties. With this approach, each instance ends up with its own copy of the instance properties, but they all share references to methods, conserving memory. This pattern allows arguments to be passed into the constructor as well, effectively combining the best parts of each pattern. The previous example can now be rewritten as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['Shelby', 'Court'];
}
Person.prototype = {
constructor: Person,
sayName : function () {
alert(this.name);
}
};

var person1 = new Person('rayjune', 21, 'Software Engineer'); var person2 = new Person('Greg', 27, 'Doctor');

person1.friends.push('Van');
alert(person1.friends); // 'Shelby,Court,Van'
alert(person2.friends); // 'Shelby,Court,Van'
alert(person1.friends === person2.friends); false alert(person1.sayName === person2.sayName); //true

The hybrid constructor/prototype pattern is the most widely used and accepted practice for defining custom reference types in ECMAScript. Generally speaking, this is the default pattern to use for defining reference types.

Dynamic Prototype Pattern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name, age, job){
//properties
this.name = name;
this.age = age;
this.job = job;

//methods
if (typeof this.sayName != “function”){
Person.prototype.sayName = function(){
alert(this.name);
};
}
var friend = new Person(“Nicholas”, 29, “Software Engineer”);
friend.sayName();
}

You cannot overwrite the prototype using an object literal when using the dynamic prototype pattern. As described previously, overwriting a prototype when an instance already exists effectively cuts off that instance from the new prototype.

Durable Constructor Pattern

It have no public properties and whose methods don’t reference the this object . Durable objects are best used in secure environments (those that forbid the use of this and new) or to protect data from the rest of the application (as in mashups).

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){
//create the object to return
var o = new Object();
//optional: define private variables/functions here
//attach methods
o.sayName = function(){
alert(name);
};
//return the object
return o;
}

Note that there is no way to access the value of name from the returned object. The sayName() method has access to it, but nothing else does. The Person durable constructor is used as follows:

1
2
var friend = Person(“rayjune”, 21, “Software Engineer”);
friend.sayName(); //”rayjune”

INHERITANCE

Implementation inheritance is the only type of inheritance supported by ECMAScript, and this is done primarily through the use of prototype chaining.

Prototype Chaining

Implementing prototype chaining involves the following code pattern:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};

function SubType(){
this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};

var instance = new SubType();
alert(instance.getSuperValue()); //true

When inheritance has been implemented via prototype chaining, that search can continue up the prototype chain. In the previous example, for instance, a call to instance.getSuperValue() results in a three-step search:

  1. the instance,
  2. SubType.prototype
  3. SuperType.prototype, where the method is found.
Don’t Forget Default Prototypes

In reality, there is another step in the prototype chain. All reference types inherit from Object by default, which is accomplished through prototype chaining. The default prototype for any function is an instance of Object, meaning that its internal prototype pointer points to Object .prototype.

Define Prototype and Instance Relationships

instanceOf()

1
2
3
alert(instance instanceof Object); 		//true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

isPrototypeOf()

1
alert(Object.prototype.isPrototypeOf(instance)); 	 alert(SuperType.prototype.isPrototypeOf(instance));  alert(SubType.prototype.isPrototypeOf(instance)); //3 all true
Working with Methods discreetly

It’s a important thing to understand is that the object literal approach to creating prototype methods cannot be used with prototype chaining, because you end up overwriting the chain. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}

//inherit from SuperType
SubType.prototype = new SuperType();

//try to add new methods - this nullifies the previous line
SubType.prototype = {
getSubValue : function (){
return this.subproperty;
},
someOtherMethod : function (){
return false;
}
};

var instance = new SubType();
alert(instance.getSuperValue()); //error
Problems with Prototype Chaining

The major issue revolves around prototypes that contain reference values.this is why properties are typically defi ned within the constructor instead of on the prototype.

Constructor Stealing

use apply() & call()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function SuperType(){
this.colors = [“red”, “blue”, “green”];
}
function SubType(){
//inherit from SuperType
SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push(“black”);
alert(instance1.colors); //”red,blue,green,black”

var instance2 = new SubType();
alert(instance2.colors); //”red,blue,green”

One advantage that constructor stealing offers over prototype chaining is the ability to pass arguments into the supertype constructor from within the subtype constructor. Consider the following:

1
2
3
4
5
6
7
8
9
10
11
function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this, “Nicholas”);
this.age = 29;
}

var instance = new SubType();
alert(instance.name); //”Nicholas”;
alert(instance.age); //29

Combination Inheritance

Function Expressions

One of the more powerful, and often confusing, parts of JavaScript is function expressions. As mentioned in Chapter 5, there are two ways to define a function: by

  • function declaration
  • by function expression.

The first, function declaration, has the following form

1
2
3
function functionName(arg0, arg1, arg2) {
//function body
}

The name of the function follows the function keyword, and this is how the function’s name is assigned. Firefox, Safari, Chrome, and Opera all feature a nonstandard name property on functions exposing the assigned name. This value is always equivalent to the identifier that immediately follows the function keyword:

1
2
alert(functionName.name); //”functionName”
//works only in Firefox, Safari, Chrome, and Opera

One of the key characteristics of function declarations is function declaration hoisting, whereby function declarations are read before the code executes. That means a function declaration may appear after code that calls it and still work:

1
2
3
4
sayHi();
function sayHi(){
alert(“Hi!”);
}

The second way to create a function is by using a function expression. Function expressions have several forms. The most common is as follows:

1
2
3
var functionName = function(arg0, arg1, arg2){
//function body
};

The created function is considered to be an anonymous function, because it has no identifier after the function keyword. (Anonymous functions are also sometimes called lambda functions.) This means the name property is the empty string.

Understanding function hoisting is key to understanding the differences between function declarations and function expressions.

RECURSION

A recursive function typically is formed when a function calls itself by name, as in the following example:

1
2
3
4
5
6
7
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * factorial(num-1);
}
}

This is the classic recursive factorial function. Although this works initially, it’s possible to prevent it from functioning by running the following code immediately after it:

1
2
3
var anotherFactorial = factorial; 
factorial = null;
alert(anotherFactorial(4)); //error!

Recall that arguments.callee is a pointer to the function being executed and, as such, can be used to call the function recursively, as shown here:

1
2
3
4
5
6
7
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num-1);
}
}

But the value of arguments.callee is not accessible to a script running in strict mode and will cause an error when attempts are made to read it. Instead, you can use named function expressions to achieve the same result. For example:

1
2
3
4
5
6
7
var factorial = (function f(num){
if (num <= 1){
return 1;
} else {
return num * factorial(num-1);
}
}

In this code, a named function expression f() is created and assigned to the variable factorial. The name f remains the same even if the function is assigned to another variable, so the recursive call will always execute correctly. This pattern works in both nonstrict mode and strict mode.

CLOSURES

Closures are functions that have access to variables from another function’s scope.

This is often accomplished by creating a function inside a function, as in the following highlighted lines from the previous createComparisonFunction() example:

1
2
3
4
5
6
7
8
9
10
11
12
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 0;
}
else {return 0;}
};
}

When a function is called, an execution context is created, and its scope chain is created. The activation object for the function is initialized with values for arguments and any named arguments. The outer function’s activation object is the second object in the scope chain. This process continues for all containing functions until the scope chain terminates with the global execution context.

Note that the scope chain is essentially a list of pointers to variable objects and does not physically contain the objects.

Whenever a variable is accessed inside a function, the scope chain is searched for a variable with the given name. Once the function has completed, the local activation object is destroyed, leaving only the global scope in memory. Closures, however, behave differently.

After createComparisonFunction() completes, the scope chain for its execution context is destroyed, but its activation object will remain in memory until the anonymous function is destroyed, as in the following:

1
2
3
4
5
6
//create function
var compareNames = createComparisonFunction(“name”);
//call function
var result = compareNames({ name: “Nicholas” }, { name: “Greg”});
compareNames = null;
//dereference function - memory can now be reclaimed

( Since closures carry with them the containing function’s scope, they take up more memory than other functions. Overuse of closures can lead to excess memory consumption, so it’s recommended you use them only when absolutely necessary. Optimizing JavaScript engines, such as V8, make attempts to reclaim memory that is trapped because of closures, but it’s still recommended to be careful when using closures. )

Closures and Variables

Remember that the closure stores a reference to the entire variable object, not just to a particular variable. This issue is illustrated clearly in the following example:

1
2
3
4
5
6
7
8
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
return result;
}

You can, however, force the closures to act appropriately by creating another anonymous function, as follows:

1
2
3
4
5
6
7
8
9
10
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
return result;
}

The this Object

1
2
3
4
5
6
7
8
9
10
11
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"

Before defining the anonymous function, a variable named that is assigned equal to the this object. When the closure is defined, it has access to that, since it is a uniquely named variable in the containing function. Even after the function is returned, that is still bound to object, so calling object .getNameFunc()() returns “My Object”.

Both this and arguments behave in this way. If you want access to a containing scope’s arguments object, you’ll need to save a reference into another variable that the closure can access.

MIMICKING BLOCK SCOPE

1
2
3
4
5
6
(function(){
var now = new Date();
if (now.getMonth() == 0 && now.getDate() == 1){
alert("Happy new year!");
}
})();

This pattern limits the closure memory problem, because there is no reference to the anonymous function. Therefore the scope chain can be destroyed immediately after the function has completed.

PRIVATE VARIABLES

Any variable defi ned inside a function is

considered private since it is inaccessible outside that function. This includes function arguments, local variables, and functions defi ned inside other functions. Consider the following:

1
2
3
4
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
1
2
3
4
5
6
7
8
9
10
11
12
function Person(name){
this.getName = function(){
return name;
};
this.setName = function (value) {
name = value;
};
}
var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Greg");
alert(person.getName()); //"Greg"

As discussed in Chapter 6, the constructor pattern is flawed in that new methods are created for each instance. Using static private variables to achieve privileged methods avoids this problem.

Static Private Variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function(){
var name = "";
Person = function(value){
name = value;
};
Person.prototype.getName = function(){
return name;
};
Person.prototype.setName = function (value){
name = value;
};
})();

var person1 = new Person("Nicholas"); alert(person1.getName()); //Nicholas
person1.setName("Greg");
alert(person1.getName()); //"Greg"

Creating static private variables in this way allows for better code reuse through prototypes, although each instance doesn’t have its own private variable. Ultimately, the decision to use instance or static private variables needs to be based on your individual requirements.

The farther up the scope chain a variable lookup is, the slower the lookup becomes because of the use of closures and private variables.

The Module Pattern

The module pattern augments the basic singleton to allow for private variables and privileged methods, taking the following format:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var singleton = function(){
//private variables and functions
var privateVariable = 10;
function privateFunction(){
return false;
}
//privileged/public methods and properties
return {
publicProperty: true,
publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();

The Browser Object Model

The Document Object Model

The Document Object Model (DOM) is an application programming interface (API) for HTML and XML documents. The DOM represents a document as a hierarchical tree of nodes, allowing developers to add, remove, and modify individual parts of the page.

HIERARCHY OF NODES

The Node Type

Node Relationships

Each node has a childNodes property containing a NodeList. A NodeList is an array-like object used to store an ordered list of nodes that are accessible by position.

Keep in mind that a NodeList is not an instance of Array even though its values can be accessed using bracket notation and the length property is present.

NodeList objects are unique in that they are actually queries being run against the DOM structure, so changes will be reflected in NodeList objects automatically. It is often said that a NodeList is a living, breathing object rather than a snapshot of what happened at the time it was fi rst accessed.

The following example shows how nodes stored in a NodeList may be accessed via bracket notation or by using the item() method:

1
2
3
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;

It’s possible to convert NodeList objects into arrays using Array.prototype.slice() as was discussed earlier for the arguments object. Consider the following example:

1
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);

Each node has a parentNode property pointing to its parent in the document tree. All nodes contained within a childNodes list have the same parent, so each of their parentNode properties points to the same node.

Also previousSibling & nextSibling

With all of these relationships, the childNodes property is really more of a convenience than a necessity, since it’s possible to reach any node in a document tree by simply using the relationship pointers. Another convenience method is hasChildNodes(), which returns true if the node has one or more child nodes and is more efficient than querying the length of the childNodes list.

One final relationship is shared by every node. The ownerDocument property is a pointer to the document node that represents the entire document. Nodes are considered to be owned by the document in which they were created (typically the same in which they reside), because nodes cannot exist simultaneously in two or more documents. This property provides a quick way to access the document node without needing to traverse the node hierarchy back up to the top.

Not all node types can have child nodes even though all node types inherit from Node.

Manipulating Nodes

Because all relationship pointers are read-only, several methods are available to manipulate nodes.

The most often-used method is appendChild(), which adds a node to the end of the childNodes list. Doing so updates all of the relationship pointers in the newly added node, the parent node, and the previous last child in the childNodes list. When complete, appendChild() returns the newly added node. Here is an example:

1
2
var returnedNode = someNode.appendChild(newNode); alert(returnedNode == newNode); 	//true
alert(someNode.lastChild == newNode); //true

If the node passed into appendChild() is already part of the document, it is removed from its previous location and placed at the new location. Even though the DOM tree is connected by a series of pointers, no DOM node may exist in more than one location in a document. So if you call appendChild()and pass in the first child of a parent, as the following example shows, it will end up as the last child:

1
2
3
//assume multiple children for someNode
var returnedNode = someNode.appendChild(someNode.firstChild); //false
alert(returnedNode == someNode.firstChild); alert(returnedNode == someNode.lastChild); //true

insertChild()

1
2
3
4
5
6
7
8
9
//insert as last child
returnedNode = someNode.insertBefore(newNode, null); alert(newNode == someNode.lastChild); //true

//insert as the new first child
returnedNode = someNode.insertBefore(newNode, someNode.firstChild); alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true

//insert before last child
returnedNode = someNode.insertBefore(newNode, someNode.lastChild); alert(newNode == someNode.childNodes[someNode.childNodes.length-2]);

replaceChild()

1
2
3
4
//replace first child
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
//replace last child
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);

removeChild()

1
2
3
4
//remove first child
var formerFirstChild = someNode.removeChild(someNode.firstChild);
//remove last child
var formerLastChild = someNode.removeChild(someNode.lastChild);

All four of these methods work on the immediate children of a specific node, meaning that to use them you must know the immediate parent node (which is accessible via the previously mentioned parentNode property). Not all node types can have child nodes, and these methods will throw errors if you attempt to use them on nodes that don’t support children.

Other Methods

Two other methods are shared by all node types. The first is cloneNode(), which creates an exact clone of the node on which it’s called.

The cloneNode() method accepts a single Boolean argument indicating whether to do a deep copy. When the argument is true, a deep copy is used, cloning the node and its entire subtree; when false, only the initial node is cloned.

The cloned node that is returned is owned by the document but has no parent node assigned. As such, the cloned node is an orphan and doesn’t exist in the document until added via appendChild(), insertBefore(), or replaceChild(). For example, consider the following HTML:

1
2
3
4
5
<ul>
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>

If a reference to this

    element is stored in a variable named myList, the following code shows the two modes of the cloneNode() method:

1
2
3
var deepList = myList.cloneNode(true); alert(deepList.childNodes.length); //3

var shallowList = myList.cloneNode(false); alert(shallowList.childNodes.length); //0

The cloneNode() method doesn’t copy JavaScript properties that you add to DOM nodes, such as event handlers. This method copies only attributes and, optionally, child nodes.

The last remaining method is normalize().

Its sole job is to deal with text nodes in a document subtree. Because of parser implementations or DOM manipulations, it’s possible to end up with text nodes that contain no text or text nodes that are siblings.

When normalize() is called on a node, that node’s descendants are searched for both of these circumstances. If an empty text node is found, it is removed; if text nodes are immediate siblings, they are joined into a single text node. This method is discussed further later on in this chapter.

The Document Type

JavaScript represents document nodes via the Document type. In browsers, the document object is an instance of HTMLDocument (which inherits from Document) and represents the entire HTML page. The document object is a property of window and so is accessible globally. A Document node has the following characteristics:

  • nodeType is 9.
  • nodeName is “#document”.
  • nodeValue is null.
  • parentNode is null.
  • ownerDocument is null.
  • Child nodes may be a DocumentType (maximum of one), Element (maximum of one), ProcessingInstruction, or Comment.

The document object can be used to get information about the page and to manipulate both its appearance and the underlying structure.

Document Children

There are two built-in shortcuts to child nodes.

The first is the documentElement property, which always points to the <html> element in an HTML page. The document element is always represented in the childNodes list as well, but the documentElement property gives faster and more direct access to that element. Consider the following simple page:

1
2
3
4
<html>
<body>
</body>
</html>

When this page is parsed by a browser, the document has only one child node, which is the element. This element is accessible from both documentElement and the childNodes list, as shown here:

1
2
3
var html = document.documentElement;  //get reference to <html>  
alert(html === document.childNodes[0]); //true
alert(html === document.firstChild); //true

As an instance of HTMLDocument, the document object also has a body property that points to the <body> element directly. Since this is the element most often used by developers, document.body tends to be used quite frequently in JavaScript, as this example shows:

1
var body = document.body; 	//get referene to <body>

For the most part, the appendChild(), removeChild(), and replaceChild() methods aren’t used on document, since the document type (if present) is read-only and there can be only one element child node (which is already present).

Document Information
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//get the document title
var originalTitle = document.title;

//set the document title
document.title = “New page title”; //no use

//get the complete URL
var url = document.URL;

//get the domain
var domain = document.domain;

//set the domain


//get the referrer
var referrer = document.referrer;
Locating Elements

getElementById() & getElementByTagName()

1
<div id="myDiv">Some text</div>
1
2
3
var div = document.getElementById("myDiv");	//retrieve reference to the <div>

var div = document.getElementById(“mydiv”); //won’t work

If there is more than one element with the same ID in a page, getElementById() returns the element that appears first in the document.

The getElementsByTagName() method is another commonly used method for retrieving element references. It accepts a single argument — the tag name of the elements to retrieve — and returns a NodeList containing zero or more elements.

In HTML documents, this method returns an HTMLCollection object, which is very similar to a NodeList in that it is considered a “live” collection. For example, the following code retrieves all elements in the page and returns an HTMLCollection:

1
2
3
4
5
var images = document.getElementsByTagName(“img”);

alert(images.length); //output the number of images
alert(images[0].src); //output the src attribute of the first image
alert(images.item(0).src); //output the src attribute of the first image

The HTMLCollection object has an additional method, namedItem(), that lets you reference an item in the collection via its name attribute.

1
<img src=”myimage.gif” name=”myImage”>

A reference to this element can be retrieved from the images variable like this:

1
var myImage = images.namedItem(“myImage”);

To retrieve all elements in the document, pass in “*” to getElementsByTagName(). The asterisk is generally understood to mean “all” in JavaScript and Cascading Style Sheets (CSS). Here’s an example:

1
var allElements = document.getElementsByTagName(“*”);

A third method, which is defi ned on the HTMLDocument type only, is getElementsByName().

1
2
3
4
5
6
7
8
9
10
11
<fieldset>
<legend>Which color do you prefer?</legend>
<ul>
<li><input type=”radio” value=”red” name=”color” id=”colorRed”>
<label for=”colorRed”>Red</label></li>
<li><input type=”radio” value=”green” name=”color” id=”colorGreen”>
<label for=”colorGreen”>Green</label></li>
<li><input type=”radio” value=”blue” name=”color” id=”colorBlue”>
<label for=”colorBlue”>Blue</label></li>
</ul>
</fieldset>
1
var radios = document.getElementsByName(“color”);
Special Collections
  • document.forms — Contains all
    elements in the document. The same as document .getElementsByTagName(“form”).
  • document.images — Contains all elements in the document. The same as document .getElementsByTagName(“img”).
  • document.links — Contains all elements with an href attribute in the document.
Document Writing

write() & writeln()

The Element Type

Next to the Document type, the Element type is most often used in web programming.

HTML Elements

All HTML elements are represented by the HTMLElement type, either directly or through subtyping. The HTMLElement inherits directly from Element and adds several properties. Each property represents one of the following standard attributes that are available on every HTML element:

  • id — A unique identifier for the element in the document.

  • title — Additional information about the element, typically represented as a tooltip.

  • dir — The direction of the language, “ltr” (left-to-right) or “rtl” (right-to-left); also rarely used.

  • className — The equivalent of the class attribute, which is used to specify CSS classes on an element. The property could not be named class because class is an ECMAScript reserved word.

Getting Attributes

The three primary DOM methods for working with attributes are getAttribute(), setAttribute(), and removeAttribute(). These methods are intended to work on any attribute, including those defi ned as properties on the HTMLElement type. Here’s an example:

1
2
3
4
5
6
var div = document.getElementById(“myDiv”);
alert(div.getAttribute(“id”)); //”myDiv”
alert(div.getAttribute(“class”)); //”bd”
alert(div.getAttribute(“title”)); //”Body text”
alert(div.getAttribute(“lang”)); //”en”
alert(div.getAttribute(“dir”)); //”ltr”

The getAttribute() method can also retrieve the value of custom attributes that aren’t part of the formal HTML language. Consider the following element:

1
<div id=”myDiv” my_special_attribute=”hello!”></div>

In this element, a custom attribute named my_special_attribute is defi ned to have a value of “hello!”. This value can be retrieved using getAttribute() just like any other attribute, as shown here:

1
var value = div.getAttribute(“my_special_attribute”);

Also note that, according to HTML5, custom attributes should be prepended with data- in order to validate.

But getAttribute can’t get “style”(CSS) and “onclick”(JS event-handler attributes) code.

1
2
3
div.getAttribute("style") //null
div //<button id=​"done">​all done</button>​
div.getAttribute("onclick"); //null

Because of these differences, developers tend to forego getAttribute() when programming the DOM in JavaScript and instead use the object properties exclusively. The getAttribute() method is used primarily to retrieve the value of a custom attribute.

Setting Attributes
1
2
3
4
5
div.setAttribute(“id”, “someOtherId”);
div.setAttribute(“class”, “ft”);
div.setAttribute(“title”, “Some other text”);
div.setAttribute(“lang”,”fr”);
div.setAttribute(“dir”, “rtl”);

div.removeAttribute(“class”); rarely used.

Creating Elements
1
2
3
var div = document.createElement(“div”);
div.id = "myNewDiv";
div.className = "box";

Setting these attributes on the new element assigns information only. Since the element is not part of the document tree, it doesn’t affect the browser’s display. The element can be added to the document tree using appendChild(), insertBefore(), or replaceChild(). The following code adds the newly created element to the document’s <body> element:

1
document.body.appendChild(div);

Once the element has been added to the document tree, the browser renders it immediately. Any changes to the element after this point are immediately reflected by the browser.

Element Children
1
2
3
4
5
<ul id=”myList”>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>

When this code is parsed in Internet Explorer 8 and earlier, the <ul> element has 2 child nodes, one for each of the <li> elements. In all other browsers, the <ul> element has 7 elements: three <li> elements and four text nodes representing the white space between <li> elements. If the white space between elements is removed, as the following example demonstrates, all browsers return the same number of child nodes:

1
<ul id=”myList”><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>

The Text Type

1
2
3
var textNode = div.firstChild; 	//or div.childNodes[0]
//Once a reference to the text node is retrieved, it can be changed like this:
div.firstChild.nodeValue = “Some other message”;

createTextNode

1
var textNode = document.createTextNode("<strong>Hello</strong> world!");

When a new text node is created, its ownerDocument property is set, but it does not appear in the browser window until it is added to a node in the document tree. The following code creates a new <div> element and adds a message to it:

1
document.body.appendChild(textNode);

WORKING WITH THE DOM

Dynamic Scripts

Dynamic scripts are those that don’t exist when the page is loaded but are included later by using the DOM.

As with the HTML element, there are two ways to do this: pulling in an external fi le or inserting text directly.

1
2
 var script = document.createElement(“script”); script.type = “text/javascript”;
script.src = “client.js”; document.body.appendChild(script);

just like

1
<script type=”text/javascript” src=”client.js”></script>
1
2
3
4
5
6
function loadScript(url){
var script = document.createElement(“script”);
script.type = “text/javascript”;
script.src = url;
document.body.appendChild(script);
}

just like

1
loadScript("client.js");

and inline style

1
2
3
var script = document.createElement("script");
script.type = "text/javascript";
script.appendChild(document.createTextNode("function sayHi(){alert('hi');}")); document.body.appendChild(script);
1
2
3
4
5
<script type="text/javascript">
function sayHi(){
alert("hi");
}
</script>

Dynamic Styles

CSS styles are included in HTML pages using one of two elements. The <link> element is used to include CSS from an external file, whereas the <style> element is used to specify inline styles.

Similar to dynamic scripts, dynamic styles don’t exist on the page when it is loaded initially; rather, they are added after the page has been loaded.

Manipulating Tables

Using NodeLists

Understanding a NodeList object and its relatives, NamedNodeMap and HTMLCollection, is critical to a good understanding of the DOM as a whole. Each of these collections is considered “live,”
which is to say that they are updated when the document structure changes such that they are always current with the most accurate information. In reality, all NodeList objects are queries that are run against the DOM document whenever they are accessed. For instance, the following results in an infi nite loop:

1
2
3
4
5
6
7
8
var divs = document.getElementsByTagName(“div”),
i,
div;

for (i=0; i < divs.length; i++){
div = document.createElement("div");
document.body.appendChild(div);
}

should change like this

1
2
3
4
for (i=0, len=divs.length; i < len; i++){
div = document.createElement(”div”);
document.body.appendChild(div);
}

Generally speaking, it is best to limit the number of times you interact with a NodeList. Since a query is run against the document each time, try to cache frequently used values retrieved from a NodeList.

Summary

Perhaps the most important thing to understand about the DOM is how it affects overall performance. DOM manipulations are some of the most expensive operations that can be done in JavaScript, with NodeList objects being particularly troublesome. NodeList objects are “live,” meaning that a query is run every time the object is accessed. Because of these issues, it is best to minimize the number of DOM manipulations.

DOM Extensions

SELECTORS API

One of the most popular capabilities of JavaScript libraries is the ability to retrieve a number of DOM elements matching a pattern specified using CSS selectors. Indeed, the library jQuery is built completely around the CSS selector queries of a DOM document in order to retrieve references to elements instead of using getElementById() and getElementsByTagName().

The querySelector() Method

1
2
3
4
5
6
7
8
9
10
11
//get the body element
var body = document.querySelector(“body”);

//get the element with the ID “myDiv”
var myDiv = document.querySelector(“#myDiv”);

//get first element with a class of “selected”
var selected = document.querySelector(“.selected”);

//get first image with class of “button”
var img = document.body.querySelector(“img.button”);

The querySelectorAll() Method

The querySelectorAll() method accepts the same single argument as querySelector() — the CSS query — but returns all matching nodes instead of just one. This method returns a static instance of NodeList.

To clarify, the return value is actually a NodeList with all of the expected properties and methods, but its underlying implementation acts as a snapshot of elements rather than a dynamic query that is constantly reexecuted against a document. This implementation eliminates most of the performance overhead associated with the use of NodeList objects.

Any call to querySelectorAll() with a valid CSS query will return a NodeList object regardless of the number of matching elements; if there are no matches, the NodeList is empty.

As with querySelector(), the querySelectorAll() method is available on the Document, DocumentFragment, and Element types. Here are some examples:

1
2
3
4
5
6
//get all <em> elements in a <div> (similar to getElementsByTagName(“em”)) var ems = document.getElementById(“myDiv”).querySelectorAll(“em”);

//get all elements with class of “selected”
var selecteds = document.querySelectorAll(“.selected”);

//get all <strong> elements inside of <p> elements var strongs = document.querySelectorAll(“p strong”);

The resulting NodeList object may be iterated over using either item() or bracket notation to retrieve individual elements. Here’s an example:

1
2
3
4
5
var i, len, strong;
for (i=0, len=strongs.length; i < len; i++){
strong = strongs[i]; //or strongs.item(i)
strong.className = “important”;
}

The matchesSelector() Method

The Selectors API Level 2 specification introduces another method called matchesSelector() on the Element type. This method accepts a single argument, a CSS selector, and returns true if the given element matches the selector or false if not. For example:

1
2
3
if (document.body.matchesSelector(“body.page1”)){
//true
}

ELEMENT TRAVERSAL

  • childElementCount — Returns the number of child elements (excludes text nodes and comments).
  • firstElementChild — Points to the fi rst child that is an element. Element-only version of firstChild.
  • lastElementChild — Points to the last child that is an element. Element-only version of previousSibling.
  • nextElementSibling — Points to the next sibling that is an element. Element-only version of nextSibling.

Supporting browsers add these properties to all DOM elements to allow for easier traversal of DOM elements without the need to worry about white space text nodes.

HTML5

The HTML5 specification, on the other hand, contains a large amount of JavaScript APIs designed for use with the markup additions. Part of these APIs overlap with the DOM and defi ne DOM extensions that browsers should provide.

Because the scope of HTML5 is vast, this section focuses only on the parts that affect all DOM nodes.

To adapt to developers and their newfound appreciation of the class attribute, HTML5 introduces a number of changes to make CSS class usage easier.

The getElementsByClassName() Method

The getElementsByClassName() method accepts a single argument, which is a string containing one or more class names, and returns a NodeList containing all elements that have all of the specified classes applied. If multiple class names are specified, then the order is considered unimportant. Here are some examples:

1
2
3
4
5
//get all elements with a class containing “username” and “current”, though it //doesn’t matter if one is declared before the other
var allCurrentUsernames = document.getElementsByClassName(“username current”);

//get all elements with a class of “selected” that exist in myDiv’s subtree
var selected = document.getElementById(“myDiv”).getElementsByClassName(“selected”);

This method is useful for attaching events to classes of elements rather than using IDs or tag names. Keep in mind that since the returned value is a NodeList, there are the same performance issues as when you’re using getElementsByTagName() and other DOM methods that return NodeList objects.

The classList Property

In class name manipulation, the className property is used to add, remove, and replace class names. Since className contains a single string, it’s necessary to set its value every time a change needs to take place, even if there are parts of the string that should be unaffected. For example, consider the following HTML code:

1
<div class=”bd user disabled”>...</div>

HTML5 introduces a way to manipulate class names in a much simpler and safer manner through the addition of the classList property for all elements. The classList property is an instance of a new type of collection named DOMTokenList. As with other DOM collections, DOMTokenList has a length property to indicate how many items it contains, and individual items may be retrieved via the item() method or using bracket notation. It also has the following additional methods:

  • add(value) — Adds the given string value to the list. If the value already exists, it will not be added.
  • contains(value) — Indicates if the given value exists in the list (true if so; false if not).
  • remove(value) — Removes the given string value from the list.
  • toggle(value) — If the value already exists in the list, it is removed. If the value doesn’t exist, then it’s added.

Using this code ensures that the rest of the class names will be unaffected by the change. The other methods also greatly reduce the complexity of the basic operations, as shown in these examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//remove the “disabled” class 
div.classList.remove(“disabled”);

//add the “current” class
div.classList.add(“current”);

//toggle the “user” class
div.classList.toggle(“user”);

//figure out what’s on the element now
if (div.classList.contains(“bd”) && !div.classList.contains(“disabled”)){
//do something
)

//iterate over the class names
for (var i=0, len=div.classList.length; i < len; i++){
doSomething(div.classList[i]);
}

The addition of the classList property makes it unnecessary to access the className property unless you intend to completely remove or completely overwrite the element’s class attribute.

Focus Management

HTML5 adds functionality to aid with focus management in the DOM. The fi rst is document .activeElement, which always contains a pointer to the DOM element that currently has focus. An element can receive focus automatically as the page is loading, via user input (typically using the Tab key), or programmatically using the focus() method. For example:

1
2
var button = document.getElementById(“myButton”); button.focus();
alert(document.activeElement === button); //true

By default, document.activeElement is set to document.body when the document is fi rst loaded. Before the document is fully loaded, document.activeElement is null.

The second addition is document.hasFocus(), which returns a Boolean value indicating if the document has focus:

1
2
3
var button = document.getElementById(“myButton”); 
button.focus();
alert(document.hasFocus()); //true

Determining if the document has focus allows you to determine if the user is interacting with the page.

Changes to HTMLDocument

The readyState Property
  • loading — The document is loading.
  • complete — The document is completely loaded.
Compatibility Mode
The head Property

Custom Data Attributes

data-

1
<div id=”myDiv” data-appId=”12345” data-myname=”Nicholas”></div>
1
2
3
var div = document.getElementById(“myDiv”);
//get the values
var appId = div.dataset.appId;

Markup Insertion (important)

The innerHTML Property
1
2
3
4
5
6
7
8
<div id=”content”>
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>

For the

element in this example, the innerHTML property returns the following string:

1
2
3
4
5
6
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>

The innerHTML property is not available on all elements. The following elements do not support innerHTML: , , , , ,

文章标题:JavaScript 高级程序设计小记

文章作者:RayJune

时间地点:下午 3:56,于知名 2-201 自习教室

原始链接:https://www.rayjune.me/2017/08/13/Professional-JavaScript-for-Web-Developers-note/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。