Eloquent Javascript

#Eloquent JavaScript

#3 FUNCHTIONS

函数体内可以嵌套函数。

只有函数体才可以定义作用域,其它用{}定义的区域,都将作用于全局。

函数变量可以被重新赋值。

函数在作用域的任何地方都可以定义,并且在任何地方都可以使用,不会按照先后顺序执行。

不要在条件语句以及循环语句中定义函数。

递归调用会比循环引用的速度慢很多,应该避免递归。

准则:

  • The basic rule, which has been repeated by many programmers and with which I wholeheartedly agree, is to not worry about efficiency until you know for sure that the program is too slow.

  • A useful principle is not to add cleverness unless you are absolutely sure you’re going to need it.

  • A pure function is a specific kind of value-producing function that not only has no side effects but also doesn’t rely on side effects from other code.

    Still, there’s no need to feel bad when writing functions that are not pure or to wage a holy war to purge them from your code. Side effects are often useful.

#4 Data Structures: Objects and Arrays

###Data sets

###Properties
Almost all JavaScript values have properties. The exceptions are null and undefined. If you try to access a property on one of these nonvalues, you get an error.

The two most common ways to access properties in JavaScript are with a dot and with square brackets. Both value.x and value[x] access a property on value—but not necessarily the same property. The difference is in how x is interpreted. When using a dot, the part after the dot must be a valid variable name, and it directly names the property. When using square brackets, the expression between the brackets is evaluated to get the property name. Whereas value.x fetches the property of value named “x”, value[x] tries to evaluate the expression x and uses the result as the property name.

###Methods

###Objects
Values of the type object are arbitrary collections of properties, and we can add or remove these properties as we please. One way to create an object is by using a curly brace "{}" notation.

Inside the curly braces, we can give a list of properties separated by commas ",". Each property is written as a name, followed by a colon ":", followed by an expression that provides a value for the property. Spaces and line breaks are not significant. When an object spans multiple lines, indenting it like in the previous example improves readability. Properties whose names are not valid variable names or valid numbers have to be quoted.

1
2
3
4
var descriptions = {
work: "Went to work",
"touched tree": "Touched a tree"
};

It is possible to assign a value to a property expression with the = operator. This will replace the property’s value if it already existed or create a new property on the object if it didn’t.

The delete operator cuts off a tentacle from such an octopus. It is a unary operator that, when applied to a property access expression, will remove the named property from the object. This is not a common thing to do, but it is possible.

1
2
3
4
5
6
7
8
9
10
var anObject = {left: 1, right: 2};
console.log(anObject.left);
// → 1
delete anObject.left;
console.log(anObject.left);
// → undefined
console.log("left" in anObject);
// → false
console.log("right" in anObject);
// → true

###Mutability

###Objects as maps

A better way is to use object properties named after the event types. We can use the square bracket access notation to create and read the properties and can use the in operator to test whether a given property exists.

1
2
3
4
5
6
7
8
9
10
11
var map = {};
function storePhi(event, phi) {
map[event] = phi;
}
storePhi("pizza", 0.069);
storePhi("touched tree", -0.081);
console.log("pizza" in map);
// → true
console.log(map["touched tree"]);
// → -0.081

###Further arrayology

  • push()
  • pop()
  • shift()
  • unshift()
  • indexOf()
  • lastIndexOf()
  • slice()
  • concat()

###Strings and their properties
We can read properties like length and toUpperCase from string values. But if you try to add a new property, it doesn’t stick.

Values of type string, number, and Boolean are not objects, and though the language doesn’t complain if you try to set new properties on them, it doesn’t actually store those properties. The values are immutable and cannot be changed.

  • slice()
  • indexOf()
  • trim()
  • charAt()
  • length
  • []

###The arguments object
The arguments object has a length property that tells us the number of arguments that were really passed to the function. It also has a property for each argument, named 0, 1, 2, and so on.

If that sounds a lot like an array to you, you’re right, it is a lot like an array. But this object, unfortunately, does not have any array methods (like slice or indexOf), so it is a little harder to use than a real array.

###The Math object
As we’ve seen, Math is a grab-bag of number-related utility functions, such as Math.max (maximum), Math.min (minimum), and Math.sqrt (square root).

Many languages will stop you, or at least warn you, when you are defining a variable with a name that is already taken. JavaScript does neither, so be careful.

  • Math.floor() rounds down to the nearest whole number.
  • Math.ceil() for “ceiling”, which rounds up to a whole number.
  • Math.round() to the nearest whole number.

###The global object
The global scope, the space in which global variables live, can also be approached as an object in JavaScript. Each global variable is present as a property of this object. In browsers, the global scope object is stored in the window variable.

1
2
3
4
5
var myVar = 10;
console.log("myVar" in window);
// → true
console.log(window.myVar);
// → 10

#5. Higher-Order Functions

###Higher-order functions
Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions. If you have already accepted the fact that functions are regular values, there is nothing particularly remarkable about the fact that such functions exist. The term comes from mathematics, where the distinction between functions and other values is taken more seriously.

Higher-order functions allow us to abstract over actions, not just values. They come in several forms. For example, you can have functions that create new functions.

And you can have functions that change other functions.

1
2
3
4
5
6
7
8
9
10
11
function noisy(f) {
return function(arg) {
console.log("calling with", arg);
var val = f(arg);
console.log("called with", arg, "- got", val);
return val;
};
}
noisy(Boolean)(0);
// → calling with 0
// → called with 0 - got false

You can even write functions that provide new types of control flow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function unless(test, then) {
if (!test) then();
}
function repeat(times, body) {
for (var i = 0; i < times; i++) body(i);
}
repeat(3, function(n) {
unless(n % 2, function() {
console.log(n, "is even");
});
});
// → 0 is even
// → 2 is even

###Passing along arguments
apply method: You pass it an array (or array-like object) of arguments, and it will call the function with those arguments.

###JSON
Stands for JavaScript Object Notation.
JavaScript gives us functions, JSON.stringify and JSON.parse, that convert data from and to this format. The first takes a JavaScript value and returns a JSON-encoded string. The second takes such a string and converts it to the value it encodes.

###Filtering an array
Like forEach, filter is also a standard method on arrays. The example defined the function only in order to show what it does internally. From now on, we’ll use it like this instead:

1
2
3
4
console.log(ancestry.filter(function(person) {
return person.father == "Carel Haverbeke";
}));
// → [{name: "Carolus Haverbeke", …}]

###Transforming with map

###Summarizing with reduce

The standard array method reduce, which of course corresponds to this function, has an added convenience. If your array contains at least one element, you are allowed to leave off the start argument. The method will take the first element of the array as its start value and start reducing at the second element.

1
2
3
4
5
console.log(ancestry.reduce(function(min, cur) {
if (cur.born < min.born) return cur;
else return min;
}));
// → {name: "Pauwels van Haverbeke", born: 1535, …}

###Composability
This is fabulous for writing clear code. Unfortunately, this clarity comes at a cost.

###Binding
The bind method, which all functions have, creates a new function that will call the original function but with some of the arguments already fixed.

1
2
3
4
5
6
7
8
9
10
11
12
13
var theSet = ["Carel Haverbeke", "Maria van Brussel",
"Donald Duck"];
function isInSet(set, person) {
return set.indexOf(person.name) > -1;
}
console.log(ancestry.filter(function(person) {
return isInSet(theSet, person);
}));
// → [{name: "Maria van Brussel", …},
// {name: "Carel Haverbeke", …}]
console.log(ancestry.filter(isInSet.bind(null, theSet)));
// → … same result

The call to bind returns a function that will call isInSet with theSet as first argument, followed by any remaining arguments given to the bound function.

The first argument, where the example passes null, is used for method calls, similar to the first argument to apply.

Functions have an apply method that can be used to call them with an array specifying their arguments. They also have a bind method, which is used to create a partially applied version of the function.

#6. The Secret Life of Objects

###Methods
Usually a method needs to do something with the object it was called on. When a function is called as a method—looked up as a property and immediately called, as in object.method()—the special variable this in its body will point to the object that it was called on.

Recall that the apply and bind methods both take a first argument that can be used to simulate method calls. This first argument is in fact used to give a value to this.

There is a method similar to apply, called call. It also calls the function it is a method of but takes its arguments normally, rather than as an array. Like apply and bind, call can be passed a specific this value.

###Prototypes
In addition to their set of properties, almost all objects also have a prototype. A prototype is another object that is used as a fallback source of properties. When an object gets a request for a property that it does not have, its prototype will be searched for the property, then the prototype’s prototype, and so on.

So who is the prototype of that empty object? It is the great ancestral prototype, the entity behind almost all objects, Object.prototype.

1
2
3
4
5
console.log(Object.getPrototypeOf({}) ==
Object.prototype);
// → true
console.log(Object.getPrototypeOf(Object.prototype));
// → null

The Object.getPrototypeOf function returns the prototype of an object.

The prototype relations of JavaScript objects form a tree-shaped structure, and at the root of this structure sits Object.prototype. It provides a few methods that show up in all objects, such as toString, which converts an object to a string representation.

Many objects don’t directly have Object.prototype as their prototype, but instead have another object, which provides its own default properties. Functions derive from Function.prototype, and arrays derive from Array.prototype.Such a prototype object will itself have a prototype, often Object.prototype, so that it still indirectly provides methods like toString.

The Object.getPrototypeOf function obviously returns the prototype of an object. You can use Object.create to create an object with a specific prototype.

1
2
3
4
5
6
7
8
9
10
var protoRabbit = {
speak: function(line) {
console.log("The " + this.type + " rabbit says '" +
line + "'");
}
};
var killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "killer";
killerRabbit.speak("SKREEEE!");
// → The killer rabbit says 'SKREEEE!'

###Constructors
A more convenient way to create objects that derive from some shared prototype is to use a constructor. In JavaScript, calling a function with the new keyword in front of it causes it to be treated as a constructor. The constructor will have its this variable bound to a fresh object, and unless it explicitly returns another object value, this new object will be returned from the call.

An object created with new is said to be an instanceof its constructor.

1
2
3
4
5
6
7
8
function Rabbit(type) {
this.type = type;
}
var killerRabbit = new Rabbit("killer");
var blackRabbit = new Rabbit("black");
console.log(blackRabbit.type);
// → black

Constructors (in fact, all functions) automatically get a property named prototype, which by default holds a plain, empty object that derives from Object.prototype. Every instance created with this constructor will have this object as its prototype. So to add a speak method to rabbits created with the Rabbit constructor, we can simply do this:

1
2
3
4
5
6
Rabbit.prototype.speak = function(line) {
console.log("The " + this.type + " rabbit says '" +
line + "'");
};
blackRabbit.speak("Doom...");
// → The black rabbit says 'Doom...'

It is important to note the distinction between the way a prototype is associated with a constructor (through its prototype property) and the way objects have a prototype (which can be retrieved with Object.getPrototypeOf). The actual prototype of a constructor is Function.prototype since constructors are functions. Its prototype property will be the prototype of instances created through it but is not its own prototype.

###Overriding derived properties

When you add a property to an object, whether it is present in the prototype or not, the property is added to the object itself, which will henceforth have it as its own property. If there is a property by the same name in the prototype, this property will no longer affect the object. The prototype itself is not changed.

1
2
3
4
5
6
7
8
9
10
Rabbit.prototype.teeth = "small";
console.log(killerRabbit.teeth);
// → small
killerRabbit.teeth = "long, sharp, and bloody";
console.log(killerRabbit.teeth);
// → long, sharp, and bloody
console.log(blackRabbit.teeth);
// → small
console.log(Rabbit.prototype.teeth);
// → small

###Prototype interference
A prototype can be used at any time to add new properties and methods to all objects based on it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var map = {};
function storePhi(event, phi) {
map[event] = phi;
}
storePhi("pizza", 0.069);
storePhi("touched tree", -0.081);
Object.prototype.nonsense = "hi";
for (var name in map)
console.log(name);
// → pizza
// → touched tree
// → nonsense
console.log("nonsense" in map);
// → true
console.log("toString" in map);
// → true
// Delete the problematic property again
delete Object.prototype.nonsense;

That’s all wrong. There is no event called “nonsense” in our data set. And there definitely is no event called “toString”.

Oddly, toString did not show up in the for/in loop, but the in operator did return true for it. This is because JavaScript distinguishes between enumerable and nonenumerable properties.

All properties that we create by simply assigning to them are enumerable. The standard properties in Object.prototype are all nonenumerable, which is why they do not show up in such a for/in loop.

It is possible to define our own nonenumerable properties by using the Object.defineProperty function, which allows us to control the type of property we are creating.
hasOwnProperty will check more information that different from in operator.

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.defineProperty(Object.prototype, "hiddenNonsense",{enumerable: false, value: "hi"});
for (var name in map)
console.log(name);
// → pizza
// → touched tree
console.log(map.hiddenNonsense);
// → hi
console.log("hiddenNonsense" in map);
// → true
console.log(map.hasOwnProperty("toString"));
console.log(map.hasOwnProperty("hiddenNonsense"));
// → false
// → false

###Prototype-less objects

We saw the Object.create function, which allows us to create an object with a specific prototype. You are allowed to pass null as the prototype to create a fresh object with no prototype. For objects like map, where the properties could be anything, this is exactly what we want.

1
2
3
4
5
6
7
8
var map = Object.create(null);
map["pizza"] = 0.069;
console.log("toString" in map);
// → false
console.log("pizza" in map);
// → true
console.log(Object.getPrototypeOf(map));
// → null

###Polymorphism

Method polymorphism. This is a simple instance of a powerful idea. When a piece of code is written to work with objects that have a certain interface—in this case, a toString method—any kind of object that happens to support this interface can be plugged into the code, and it will just work.

###Getters and setters

JavaScript provides a technique that gets us the best of both worlds. We can specify properties that, from the outside, look like normal properties but secretly have methods associated with them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var pile = {
elements: ["eggshell", "orange peel", "worm"],
get height() {
return this.elements.length;
},
set height(value) {
console.log("Ignoring attempt to set height to", value);
}
};
console.log(pile.height);
// → 3
pile.height = 100;
// → Ignoring attempt to set height to 100

In object literal, the get or set notation for properties allows you to specify a function to be run when the property is read or written. You can also add such a property to an existing object, for example a prototype, using the Object.defineProperty function (which we previously used to create nonenumerable properties).

1
2
3
4
5
6
7
8
9
10
Object.defineProperty(TextCell.prototype, "heightProp", {
get: function() { return this.text.length; }
});
var cell = new TextCell("no\nway");
console.log(cell.heightProp);
// → 2
cell.heightProp = 100;
console.log(cell.heightProp);
// → 2

You can use a similar set property, in the object passed to defineProperty, to specify a setter method. When a getter but no setter is defined, writing to the property is simply ignored.

###Inheritance

1
2
3
4
5
6
7
8
9
10
11
12
function RTextCell(text) {
TextCell.call(this, text);
}
RTextCell.prototype = Object.create(TextCell.prototype);
RTextCell.prototype.draw = function(width, height) {
var result = [];
for (var i = 0; i < height; i++) {
var line = this.text[i] || "";
result.push(repeat(" ", width - line.length) + line);
}
return result;
};

This pattern is called inheritance. It allows us to build slightly different data types from existing data types with relatively little work. Typically, the new constructor will call the old constructor (using the call method in order to be able to give it the new object as its this value). Once this constructor has been called, we can assume that all the fields that the old object type is supposed to contain have been added. We arrange for the constructor’s prototype to derive from the old prototype so that instances of this type will also have access to the properties in that prototype. Finally, we can override some of these properties by adding them to our new prototype.

Inheritance is a fundamental part of the object-oriented tradition, alongside encapsulation and polymorphism. But while the latter two are now generally regarded as wonderful ideas, inheritance is somewhat controversial.

###The instanceof operator
It is occasionally useful to know whether an object was derived from a specific constructor. For this, JavaScript provides a binary operator called instanceof.

The operator will see through inherited types. Almost every object is an instance of Object.

#8. Bugs and Error Handling

###Strict mode
JavaScript can be made a little more strict by enabling strict mode. This is done by putting the string "use strict" at the top of a file or a function body. Here’s an example:

1
2
3
4
5
6
7
8
function canYouSpotTheProblem() {
"use strict";
for (counter = 0; counter < 10; counter++)
console.log("Happy happy");
}
canYouSpotTheProblem();
// → ReferenceError: counter is not defined

Normally, when you forget to put var in front of your variable, as with counter in the example, JavaScript quietly creates a global variable and uses that. In strict mode, however, an error is reported instead. This is very helpful. It should be noted, though, that this doesn’t work when the variable in question already exists as a global variable, but only when assigning to it would have created it.

Another change in strict mode is that the this binding holds the value undefined in functions that are not called as methods. When making such a call outside of strict mode, this refers to the global scope object. So if you accidentally call a method or constructor incorrectly in strict mode, JavaScript will produce an error as soon as it tries to read something from this, rather than happily working with the global object, creating and reading global variables.

In short, putting a "use strict" at the top of your program rarely hurts and might help you spot a problem.

###Testing

Writing tests like this tends to produce rather repetitive, awkward code. Fortunately, there exist pieces of software that help you build and run collections of tests (test suites) by providing a language (in the form of functions and methods) suited to expressing tests and by outputting informative information when a test fails. These are called testing frameworks.

###Debugging
This is where you must resist the urge to start making random changes to the code. Instead, think. Analyze what is happening and come up with a theory of why it might be happening. Then, make additional observations to test this theory—or, if you don’t yet have a theory, make additional observations that might help you come up with one.

An alternative to using console.log is to use the debugger capabilities of your browser. Modern browsers come with the ability to set a breakpoint on a specific line of your code. This will cause the execution of the program to pause every time the line with the breakpoint is reached and allow you to inspect the values of variables at that point. I won’t go into details here since debuggers differ from browser to browser, but look in your browser’s developer tools and search the Web for more information. Another way to set a breakpoint is to include a debugger statement (consisting of simply that keyword) in your program. If the developer tools of your browser are active, the program will pause whenever it reaches that statement, and you will be able to inspect its state.

###Error propagation

###Exceptions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new Error("Invalid direction: " + result);
}
function look() {
if (promptDirection("Which way?") == "L")
return "a house";
else
return "two angry bears";
}
try {
console.log("You see", look());
} catch (error) {
console.log("Something went wrong: " + error);
}

The throw keyword is used to raise an exception. Catching one is done by wrapping a piece of code in a try block, followed by the keyword catch. When the code in the try block causes an exception to be raised, the catch block is evaluated. The variable name (in parentheses) after catch will be bound to the exception value. After the catch block finishes—or if the try block finishes without problems—control proceeds beneath the entire try/catch statement.

In this case, we used the Error constructor to create our exception value. This is a standard JavaScript constructor that creates an object with a message property. In modern JavaScript environments, instances of this constructor also gather information about the call stack that existed when the exception was created, a so-called stack trace. This information is stored in the stack property and can be helpful when trying to debug a problem: it tells us the precise function where the problem occurred and which other functions led up to the call that failed.

###Cleaning up after exceptions (finally)
There is one more feature that try statements have. They may be followed by a finally block either instead of or in addition to a catch block. A finally block means “No matter what happens, run this code after trying to run the code in the try block”. If a function has to clean something up, the cleanup code should usually be put into a finally block.

1
2
3
4
5
6
7
8
9
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
try {
return body();
} finally {
context = oldContext;
}
}

Even if we return directly from the try block, the finally block will be run.

###Selective catching
JavaScript (in a rather glaring omission) doesn’t provide direct support for selectively catching exceptions: either you catch them all or you don’t catch any. This makes it very easy to assume that the exception you get is the one you were thinking about when you wrote the catch block.

So we want to catch a specific kind of exception. We can do this by checking in the catch block whether the exception we got is the one we are interested in and by rethrowing it otherwise.

Rather, let’s define a new type of error and use instanceof to identify it.

1
2
3
4
5
6
function InputError(message) {
this.message = message;
this.stack = (new Error()).stack;
}
InputError.prototype = Object.create(Error.prototype);
InputError.prototype.name = "InputError";

###Assertions
Assertions are a tool to do basic sanity checking for programmer errors.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function AssertionFailed(message) {
this.message = message;
}
AssertionFailed.prototype = Object.create(Error.prototype);
function assert(test, message) {
if (!test)
throw new AssertionFailed(message);
}
function lastElement(array) {
assert(array.length > 0, "empty array in lastElement");
return array[array.length - 1];
}

Assertions are a way to make sure mistakes cause failures at the point of the mistake, rather than silently producing nonsense values that may go on to cause trouble in an unrelated part of the system.

#9. Regular Expressions

###Creating a regular expression
A regular expression is a type of object. It can either be constructed with the RegExp constructor or written as a literal value by enclosing the pattern in forward slash (/) characters.

1
2
var re1 = new RegExp("abc");
var re2 = /abc/;

When using the RegExp constructor, the pattern is written as a normal string, so the usual rules apply for backslashes.

The second notation, where the pattern appears between slash characters, treats backslashes somewhat differently. First, since a forward slash ends the pattern, we need to put a backslash before any forward slash that we want to be part of the pattern. In addition, backslashes that aren’t part of special character codes (like \n) will be preserved, rather than ignored as they are in strings, and change the meaning of the pattern. Some characters, such as question marks and plus signs, have special meanings in regular expressions and must be preceded by a backslash if they are meant to represent the character itself.

Knowing precisely what characters to backslash-escape when writing regular expressions requires you to know every character with a special meaning. For the time being, this may not be realistic, so when in doubt, just put a backslash before any character that is not a letter, number, or whitespace.

###Testing for matches

Regular expression objects have a number of methods. The simplest one is test. If you pass it a string, it will return a Boolean telling you whether the string contains a match of the pattern in the expression.

1
2
3
4
console.log(/abc/.test("abcde"));
// → true
console.log(/abc/.test("abxde"));
// → false

###Matching a set of characters

In a regular expression, putting a set of characters between square brackets makes that part of the expression match any of the characters between the brackets.

1
2
3
4
console.log(/[0123456789]/.test("in 1992"));
// → true
console.log(/[0-9]/.test("in 1992"));
// → true

Within square brackets, a dash (-) between two characters can be used to indicate a range of characters, where the ordering is determined by the character’s Unicode number. Characters 0 to 9 sit right next to each other in this ordering (codes 48 to 57), so [0-9] covers all of them and matches any digit.

There are a number of common character groups that have their own built-in shortcuts. Digits are one of them: \d means the same thing as [0-9].

  • \d Any digit character
  • \w An alphanumeric character (“word character”)
  • \s Any whitespace character (space, tab, newline, and similar)
  • \D A character that is not a digit
  • \W A nonalphanumeric character
  • \S A nonwhitespace character
  • . Any character except for newline
1
2
3
4
5
var dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/;
console.log(dateTime.test("30-01-2003 15:20"));
// → true
console.log(dateTime.test("30-jan-2003 15:20"));
// → false

To invert a set of characters—that is, to express that you want to match any character except the ones in the set you can write a caret (^) character after the opening bracket.

###Repeating parts of a pattern

When you put a plus sign (+) after something in a regular expression, it indicates that the element may be repeated more than once. Thus, /\d+/ matches one or more digit characters.

1
2
3
4
5
6
7
8
console.log(/'\d+'/.test("'123'"));
// → true
console.log(/'\d+'/.test("''"));
// → false
console.log(/'\d*'/.test("'123'"));
// → true
console.log(/'\d*'/.test("''"));
// → true

The star (*) has a similar meaning but also allows the pattern to match zero times. Something with a star after it never prevents a pattern from matching—it’ll just match zero instances if it can’t find any suitable text to match.

A question mark (?) makes a part of a pattern “optional”, meaning it may occur zero or one time. In the following example, the u character is allowed to occur, but the pattern also matches when it is missing.

1
2
3
4
5
var neighbor = /neighbou?r/;
console.log(neighbor.test("neighbour"));
// → true
console.log(neighbor.test("neighbor"));
// → true

Curly braces ({}) to indicate that a pattern should occur a precise number of times.Putting {4} after an element, for example, requires it to occur exactly four times. It is also possible to specify a range this way: {2,4} means the element must occur at least twice and at most four times. {5,} means five or more times.

###Grouping subexpressions
To use an operator like * or + on more than one element at a time, you can use parentheses (). A part of a regular expression that is enclosed in parentheses counts as a single element as far as the operators following it are concerned.

1
2
3
var cartoonCrying = /boo+(hoo+)+/i;
console.log(cartoonCrying.test("Boohoooohoohooo"));
// → true

The first and second + characters apply only to the second o in boo and hoo, respectively. The third + applies to the whole group (hoo+), matching one or more sequences like that.

The i at the end of the expression in the previous example makes this regular expression case insensitive, allowing it to match the uppercase B in the input string, even though the pattern is itself all lowercase.

###Matches and groups
The test method is the absolute simplest way to match a regular expression. It tells you only whether it matched and nothing else. Regular expressions also have an exec (execute) method that will return null if no match was found and return an object with information about the match otherwise.

1
2
3
4
5
var match = /\d+/.exec("one two 100");
console.log(match);
// → ["100"]
console.log(match.index);
// → 8

An object returned from exec has an index property that tells us where in the string the successful match begins.

String values have a match method that behaves similarly.

1
2
console.log("one two 100".match(/\d+/));
// → ["100"]

When the regular expression contains subexpressions grouped with parentheses, the text that matched those groups will also show up in the array. The whole match is always the first element. The next element is the part matched by the first group (the one whose opening parenthesis comes first in the expression), then the second group, and so on.

1
2
3
var quotedText = /'([^']*)'/;
console.log(quotedText.exec("she said 'hello'"));
// → ["'hello'", "hello"]

When a group does not end up being matched at all (for example, when followed by a question mark), its position in the output array will hold undefined. Similarly, when a group is matched multiple times, only the last match ends up in the array.

1
2
3
4
console.log(/bad(ly)?/.exec("bad"));
// → ["bad", undefined]
console.log(/(\d)+/.exec("123"));
// → ["123", "3"]

###The date type

1
2
3
4
5
6
console.log(new Date());
// → Wed Dec 04 2013 14:24:57 GMT+0100 (CET)
console.log(new Date(2009, 11, 9));
// → Wed Dec 09 2009 00:00:00 GMT+0100 (CET)
console.log(new Date(2009, 11, 9, 12, 59, 59, 999));
// → Wed Dec 09 2009 12:59:59 GMT+0100 (CET)

JavaScript uses a convention where month numbers start at zero (so December is 11), yet day numbers start at one. This is confusing and silly. Be careful.

The last four arguments (hours, minutes, seconds, and milliseconds) are optional and taken to be zero when not given.

Timestamps are stored as the number of milliseconds since the start of 1970, using negative numbers for times before 1970 (following a convention set by “Unix time”, which was invented around that time).

1
2
3
4
console.log(new Date(2013, 11, 19).getTime());
// → 1387407600000
console.log(new Date(1387407600000));
// → Thu Dec 19 2013 00:00:00 GMT+0100 (CET)

If you give the Date constructor a single argument, that argument is treated as such a millisecond count. You can get the current millisecond count by creating a new Date object and calling getTime on it but also by calling the Date.now function.

Date objects provide methods like getFullYear, getMonth, getDate, getHours, getMinutes, and getSeconds to extract their components. There’s also getYear, which gives you a rather useless two-digit year value (such as 93 or 14).

1
2
3
4
5
6
7
8
9
function findDate(string) {
var dateTime = /(\d{1,2})-(\d{1,2})-(\d{4})/;
var match = dateTime.exec(string);
return new Date(Number(match[3]),
Number(match[2]) - 1,
Number(match[1]));
}
console.log(findDate("30-1-2003"));
// → Thu Jan 30 2003 00:00:00 GMT+0100 (CET)

###Word and string boundaries
If we want to enforce that the match must span the whole string, we can add the markers ^ and $. The caret matches the start of the input string, while the dollar sign matches the end. So, /^\d+$/ matches a string consisting entirely of one or more digits, /^!/ matches any string that starts with an exclamation mark, and /x^/ does not match any string (there cannot be an x before the start of the string).

If, on the other hand, we just want to make sure the date starts and ends on a word boundary, we can use the marker \b. A word boundary can be the start or end of the string or any point in the string that has a word character (as in \w) on one side and a nonword character on the other.

1
2
3
4
console.log(/cat/.test("concatenate"));
// → true
console.log(/\bcat\b/.test("concatenate"));
// → false

####Choice patterns

The pipe character (|) denotes a choice between the pattern to its left and the pattern to its right. So I can say this:

1
2
3
4
5
var animalCount = /\b\d+ (pig|cow|chicken)s?\b/;
console.log(animalCount.test("15 pigs"));
// → true
console.log(animalCount.test("15 pigchickens"));
// → false

###The mechanics of matching

###Summary

  • /abc/ A sequence of characters
  • /[abc]/ Any character from a set of characters
  • /[^abc]/ Any character not in a set of characters
  • /[0-9]/ Any character in a range of characters
  • /x+/ One or more occurrences of the pattern x
  • /x+?/ One or more occurrences, nongreedy
  • /x*/ Zero or more occurrences
  • /x?/ Zero or one occurrence
  • /x{2,4}/ Between two and four occurrences
  • /(abc)/ A group
  • /a|b|c/ Any one of several patterns
  • /\d/ Any digit character
  • /\w/ An alphanumeric character (“word character”)
  • /\s/ Any whitespace character
  • /./ Any character except newlines
  • /\b/ A word boundary
  • /^/ Start of input
  • /$/ End of input

#12. JavaScript and the Browser

###Networks and the Internet

Transmission Control Protocol (TCP).

A TCP connection works as follows: one computer must be waiting, or listening, for other computers to start talking to it. To be able to listen for different kinds of communication at the same time on a single machine, each listener has a number (called a port) associated with it. Most protocols specify which port should be used by default. For example, when we want to send an email using the SMTP protocol, the machine through which we send it is expected to be listening on port 25.

Another computer can then establish a connection by connecting to the target machine using the correct port number. If the target machine can be reached and is listening on that port, the connection is successfully created. The listening computer is called the server, and the connecting computer is called the client.

###The web

To add content to the Web, all you need to do is connect a machine to the Internet, and have it listen on port 80, using the Hypertext Transfer Protocol (HTTP). This protocol allows other computers to request documents over the network.

Each document on the Web is named by a Uniform Resource Locator (URL), which looks something like this:

1
2
3
http://eloquentjavascript.net/12_browser.html
| | | |
protocol server path

###In the sandbox

#13. The Document Object Model

###Tree
We call a data structure a tree when it has a branching structure, has no cycles (a node may not contain itself, directly or indirectly), and has a single, well-defined “root”. In the case of the DOM, document.documentElementserves as the root.

Each DOM node object has a nodeType property, which contains a numeric code that identifies the type of node. Regular elements have the value 1, which is also defined as the constant property document.ELEMENT_NODE. Text nodes, representing a section of text in the document, have the value 3 (document.TEXT_NODE). Comments have the value 8 (document.COMMENT_NODE).

###Moving through the tree
The following recursive function scans a document for text nodes containing a given string and returns true when it has found one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function talksAbout(node, string) {
if (node.nodeType == document.ELEMENT_NODE) {
for (var i = 0; i < node.childNodes.length; i++) {
if (talksAbout(node.childNodes[i], string))
return true;
}
return false;
} else if (node.nodeType == document.TEXT_NODE) {
return node.nodeValue.indexOf(string) > -1;
}
}
console.log(talksAbout(document.body, "book"));
// → true

The nodeValue property of a text node refers to the string of text that it represents.

###Finding elements

  • getElementsByTagName
1
2
var link = document.body.getElementsByTagName("a")[0];
console.log(link.href);
  • getElementById

To find a specific single node, you can give it an id attribute and use document.getElementById instead.

1
2
3
4
5
6
7
<p>My ostrich Gertrude:</p>
<p><img id="gertrude" src="img/ostrich.png"></p>
<script>
var ostrich = document.getElementById("gertrude");
console.log(ostrich.src);
</script>
  • getElementsByClassName

A third, similar method is getElementsByClassName, which, like getElementsByTagName, searches through the contents of an element node and retrieves all elements that have the given string in their class attribute.

###Changing the document
Element nodes have a number of methods that can be used to change their content. The removeChild method removes the given child node from the document. To add a child, we can use appendChild, which puts it at the end of the list of children, or insertBefore, which inserts the node given as the first argument before the node given as the second argument.

A node can exist in the document in only one place. Thus, inserting paragraph “Three” in front of paragraph “One” will first remove it from the end of the document and then insert it at the front, resulting in “Three/One/Two”. All operations that insert a node somewhere will, as a side effect, cause it to be removed from its current position (if it has one).

The replaceChild method is used to replace a child node with another one. It takes as arguments two nodes: a new node and the node to be replaced. The replaced node must be a child of the element the method is called on. Note that both replaceChild and insertBefore expect the new node as their first argument.

###Creating nodes
document.createTextNode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<p>The <img src="img/cat.png" alt="Cat"> in the
<img src="img/hat.png" alt="Hat">.</p>
<p><button onclick="replaceImages()">Replace</button></p>
<script>
function replaceImages() {
var images = document.body.getElementsByTagName("img");
for (var i = images.length - 1; i >= 0; i--) {
var image = images[i];
if (image.alt) {
var text = document.createTextNode(image.alt);
image.parentNode.replaceChild(text, image);
}
}
}
</script>

document.createElement method. This method takes a tag name and returns a new empty node of the given type.

###Attributes
HTML allows you to set any attribute you want on nodes. This can be useful because it allows you to store extra information in a document. If you make up your own attribute names, though, such attributes will not be present as a property on the element’s node. Instead, you’ll have to use the getAttribute and setAttribute methods to work with them.

I recommended prefixing the names of such made-up attributes with data- to ensure they do not conflict with any other attributes.

The textContent property is used to get all the text in the node and is then set to an empty string, which has the effect of emptying the node. We loop over all matches of the keyword expression, appending the text between them as regular text nodes, and the text matched (the keywords) as text nodes wrapped in <strong> (bold) elements.

###Layout

The size and position of an element can be accessed from JavaScript. The offsetWidth and offsetHeight properties give you the space the element takes up in pixels. A pixel is the basic unit of measurement in the browser and typically corresponds to the smallest dot that your screen can display. Similarly, clientWidth and clientHeight give you the size of the space inside the element, ignoring border width.

The most effective way to find the precise position of an element on the screen is the getBoundingClientRect method. It returns an object with top, bottom, left, and right properties, indicating the pixel positions of the sides of the element relative to the top left of the screen. If you want them relative to the whole document, you must add the current scroll position, found under the global pageXOffset and pageYOffset variables.

Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time it is changed but rather wait as long as they can. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout in order to display the changed document on the screen. When a program asks for the position or size of something by reading properties such as offsetHeight or calling getBoundingClientRect, providing correct information also requires computing a layout.

###Styling

###Cascading styles

###Query selectors
The querySelectorAll method, which is defined both on the document object and on element nodes, takes a selector string and returns an array-like object containing all the elements that it matches.

The querySelector method (without the All part) works in a similar way. This one is useful if you want a specific, single element. It will return only the first matching element or null if no elements match.

###Positioning and animating

The position style property influences layout in a powerful way. By default it has a value of static, meaning the element sits in its normal place in the document. When it is set to relative, the element still takes up space in the document, but now the top and left style properties can be used to move it relative to its normal place. When position is set to absolute, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Also, its top and left properties can be used to absolutely position it relative to the top-left corner of the nearest enclosing element whose position property isn’t static, or relative to the document if no such enclosing element exists.

We can use this to create an animation. The following document displays a picture of a cat that floats around in an ellipse:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<p style="text-align: center">
<img src="img/cat.png" style="position: relative">
</p>
<script>
var cat = document.querySelector("img");
var angle = 0, lastTime = null;
function animate(time) {
if (lastTime != null)
angle += (time - lastTime) * 0.001;
lastTime = time;
cat.style.top = (Math.sin(angle) * 20) + "px";
cat.style.left = (Math.cos(angle) * 200) + "px";
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
</script>

The script uses requestAnimationFrame to schedule the animate function to run whenever the browser is ready to repaint the screen. The animate function itself again calls requestAnimationFrame to schedule the next update. When the browser window (or tab) is active, this will cause updates to happen at a rate of about 60 per second, which tends to produce a good-looking animation.

If we just updated the DOM in a loop, the page would freeze and nothing would show up on the screen. Browsers do not update their display while a JavaScript program is running, nor do they allow any interaction with the page. This is why we need requestAnimationFrame—it lets the browser know that we are done for now, and it can go ahead and do the things that browsers do, such as updating the screen and responding to user actions.

#14. Handling Events

###Event handlers

A better mechanism is for the underlying system to give our code a chance to react to events as they occur. Browsers do this by allowing us to register functions as handlers for specific events.

1
2
3
4
5
6
<p>Click this document to activate the handler.</p>
<script>
addEventListener("click", function() {
console.log("You clicked!");
});
</script>

The addEventListener function registers its second argument to be called whenever the event described by its first argument occurs.

###Events and DOM nodes

Every DOM element has its own addEventListener method, which allows you to listen specifically on that element.

1
2
3
4
5
6
7
8
<button>Click me</button>
<p>No handler here.</p>
<script>
var button = document.querySelector("button");
button.addEventListener("click", function() {
console.log("Button clicked.");
});
</script>

Giving a node an onclick attribute has a similar effect. But a node has only one onclick attribute, so you can register only one handler per node that way. The addEventListener method allows you to add any number of handlers, so you can’t accidentally replace a handler that has already been registered.

The removeEventListener method, called with arguments similar to as addEventListener, removes a handler.

1
2
3
4
5
6
7
8
9
<button>Act-once button</button>
<script>
var button = document.querySelector("button");
function once() {
console.log("Done.");
button.removeEventListener("click", once);
}
button.addEventListener("click", once);
</script>

To be able to unregister a handler function, we give it a name (such as once) so that we can pass it to both addEventListener and removeEventListeners.

###Event objects

Though we have ignored it in the previous examples, event handler functions are passed an argument: the event object. This object gives us additional information about the event. For example, if we want to know which mouse button was pressed, we can look at the event object’s which property.

1
2
3
4
5
6
7
8
9
10
11
12
<button>Click me any way you want</button>
<script>
var button = document.querySelector("button");
button.addEventListener("mousedown", function(event) {
if (event.which == 1)
console.log("Left button");
else if (event.which == 2)
console.log("Middle button");
else if (event.which == 3)
console.log("Right button");
});
</script>

The information stored in an event object differs per type of event. We’ll discuss various types later in this chapter. The object’s type property always holds a string identifying the event (for example "click" or "mousedown").

###Propagation
Event handlers registered on nodes with children will also receive some events that happen in the children. If a button inside a paragraph is clicked, event handlers on the paragraph will also receive the click event.

But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to propagate outward, from the node where it happened to that node’s parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole window get a chance to respond to the event.

At any point, an event handler can call the stopPropagation method on the event object to prevent handlers “further up” from receiving the event. This can be useful when, for example, you have a button inside another clickable element and you don’t want clicks on the button to activate the outer element’s click behavior.

Most event objects have a target property that refers to the node where they originated. You can use this property to ensure that you’re not accidentally handling something that propagated up from a node you do not want to handle.

###Default actions
Many events have a default action associated with them. If you click a link, you will be taken to the link’s target. If you press the down arrow, the browser will scroll the page down. If you right-click, you’ll get a context menu. And so on.

For most types of events, the JavaScript event handlers are called before the default behavior is performed. If the handler doesn’t want the normal behavior to happen, typically because it has already taken care of handling the event, it can call the preventDefault method on the event object.

This can be used to implement your own keyboard shortcuts or context menu. It can also be used to obnoxiously interfere with the behavior that users expect. For example, here is a link that cannot be followed:

1
2
3
4
5
6
7
8
<a href="https://developer.mozilla.org/">MDN</a>
<script>
var link = document.querySelector("a");
link.addEventListener("click", function(event) {
console.log("Nope.");
event.preventDefault();
});
</script>

Depending on the browser, some events can’t be intercepted. On Chrome, for example, keyboard shortcuts to close the current tab (Ctrl-W or Command-W) cannot be handled by JavaScript.

----------------- EVENTS --------------------

###Key events
When a key on the keyboard is pressed, your browser fires a "keydown" event. When it is released, a "keyup" event fires.

1
2
3
4
5
6
7
8
9
10
11
<p>This page turns violet when you hold the V key.</p>
<script>
addEventListener("keydown", function(event) {
if (event.keyCode == 86)
document.body.style.background = "violet";
});
addEventListener("keyup", function(event) {
if (event.keyCode == 86)
document.body.style.background = "";
});
</script>

Despite its name, "keydown" fires not only when the key is physically pushed down. When a key is pressed and held, the event fires again every time the key repeats.

The keyCode property of the event object. This is how you can identify which key is being pressed or released. Unfortunately, it’s not always obvious how to translate the numeric key code to an actual key.

For letter and number keys, the associated key code will be the Unicode character code associated with the (uppercase) letter or number printed on the key. The charCodeAt method on strings gives us a way to find this code.

1
2
3
4
console.log("Violet".charCodeAt(0));
// → 86
console.log("1".charCodeAt(0));
// → 49

Other keys have less predictable key codes. The best way to find the codes you need is usually by experimenting—register a key event handler that logs the key codes it gets and press the key you are interested in.

Modifier keys such as Shift, Ctrl, Alt, and Meta (Command on Mac) generate key events just like normal keys. But when looking for key combinations, you can also find out whether these keys are held down by looking at the shiftKey, ctrlKey, altKey, and metaKey properties of keyboard and mouse events.

The "keydown" and "keyup" events give you information about the physical key that is being pressed. But what if you are interested in the actual text being typed? Getting that text from key codes is awkward. Instead, there exists another event, "keypress", which fires right after "keydown" (and repeated along with "keydown" when the key is held) but only for keys that produce character input.

The DOM node where a key event originates depends on the element that has focus when the key is pressed. Normal nodes cannot have focus (unless you give them a tabindex attribute), but things such as links, buttons, and form fields can.

###Mouse clicks

Pressing a mouse button also causes a number of events to fire. The "mousedown" and "mouseup" events are similar to "keydown" and "keyup" and fire when the button is pressed and released. These will happen on the DOM nodes that are immediately below the mouse pointer when the event occurs.

After the "mouseup" event, a "click" event fires on the most specific node that contained both the press and the release of the button. For example, if I press down the mouse button on one paragraph and then move the pointer to another paragraph and release the button, the "click" event will happen on the element that contains both those paragraphs.

If two clicks happen close together, a "dblclick" (double-click) event also fires, after the second click event.

To get precise information about the place where a mouse event happened, you can look at its pageX and pageY properties, which contain the event’s coordinates (in pixels) relative to the top-left corner of the document.

The clientX and clientY properties are similar to pageX and pageY but relative to the part of the document that is currently scrolled into view. These can be useful when comparing mouse coordinates with the coordinates returned by getBoundingClientRect, which also returns viewport-relative coordinates.

###Mouse motion
Every time the mouse pointer moves, a "mousemove" event fires. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-dragging functionality.

We must stop resizing the bar when the mouse button is released. Unfortunately, not all browsers give "mousemove" events a meaningful which property. There is a standard property called buttons, which provides similar information, but that is also not supported on all browsers. Fortunately, all major browsers support either buttons or which, so the buttonPressed function in the example first tries buttons, and falls back to which when that isn’t available.

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
31
<p>Drag the bar to change its width:</p>
<div style="background: orange; width: 60px; height: 20px">
</div>
<script>
var lastX; // Tracks the last observed mouse X position
var rect = document.querySelector("div");
rect.addEventListener("mousedown", function(event) {
if (event.which == 1) {
lastX = event.pageX;
addEventListener("mousemove", moved);
event.preventDefault(); // Prevent selection
}
});
function buttonPressed(event) {
if (event.buttons == null)
return event.which != 0;
else
return event.buttons != 0;
}
function moved(event) {
if (!buttonPressed(event)) {
removeEventListener("mousemove", moved);
} else {
var dist = event.pageX - lastX;
var newWidth = Math.max(10, rect.offsetWidth + dist);
rect.style.width = newWidth + "px";
lastX = event.pageX;
}
}
</script>

We must stop resizing the bar when the mouse button is released. Unfortunately, not all browsers give "mousemove" events a meaningful which property. There is a standard property called buttons, which provides similar information, but that is also not supported on all browsers. Fortunately, all major browsers support either buttons or which, so the buttonPressed function in the example first tries buttons, and falls back to which when that isn’t available.

Whenever the mouse pointer enters or leaves a node, a "mouseover" or "mouseout" event fires. These two events can be used, among other things, to create hover effects, showing or styling something when the mouse is over a given element.

Unfortunately, creating such an effect is not as simple as starting the effect on "mouseover" and ending it on "mouseout". When the mouse moves from a node onto one of its children, "mouseout" fires on the parent node, though the mouse did not actually leave the node’s extent. To make things worse, these events propagate just like other events, and thus you will also receive "mouseout" events when the mouse leaves one of the child nodes of the node on which the handler is registered.

To work around this problem, we can use the relatedTarget property of the event objects created for these events. It tells us, in the case of "mouseover", what element the pointer was over before and, in the case of "mouseout", what element it is going to. We want to change our hover effect only when the relatedTarget is outside of our target node. Only in that case does this event actually represent a crossing over from outside to inside the node (or the other way around).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<p>Hover over this <strong>paragraph</strong>.</p>
<script>
var para = document.querySelector("p");
function isInside(node, target) {
for (; node != null; node = node.parentNode)
if (node == target) return true;
}
para.addEventListener("mouseover", function(event) {
if (!isInside(event.relatedTarget, para))
para.style.color = "red";
});
para.addEventListener("mouseout", function(event) {
if (!isInside(event.relatedTarget, para))
para.style.color = "";
});
</script>

I should add that a hover effect like this can be much more easily achieved using the CSS pseudoselector :hover, as the next example shows. But when your hover effect involves doing something more complicated than changing a style on the target node, you must use the trick with "mouseover" and "mouseout" events.

###Scroll events

Whenever an element is scrolled, a "scroll" event fires on it. This has various uses, such as knowing what the user is currently looking at (for disabling off-screen animations or sending spy reports to your evil headquarters) or showing some indication of progress (by highlighting part of a table of contents or showing a page number).

The following example draws a progress bar in the top-right corner of the document and updates it to fill up as you scroll down:

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
<style>
.progress {
border: 1px solid blue;
width: 100px;
position: fixed;
top: 10px; right: 10px;
}
.progress > div {
height: 12px;
background: blue;
width: 0%;
}
body {
height: 2000px;
}
</style>
<div class="progress"><div></div></div>
<p>Scroll me...</p>
<script>
var bar = document.querySelector(".progress div");
addEventListener("scroll", function() {
var max = document.body.scrollHeight - innerHeight;
var percent = (pageYOffset / max) * 100;
bar.style.width = percent + "%";
});
</script>

Giving an element a position of fixed acts much like an absolute position but also prevents it from scrolling along with the rest of the document.

The global innerHeight variable gives us the height of the window.

Calling preventDefault on a scroll event does not prevent the scrolling from happening. In fact, the event handler is called only after the scrolling takes place.

###Focus events

When an element gains focus, the browser fires a "focus" event on it. When it loses focus, a "blur" event fires.

Unlike the events discussed earlier, these two events do not propagate. A handler on a parent element is not notified when a child element gains or loses focus.

###Load event
When a page finishes loading, the "load" event fires on the window and the document body objects. This is often used to schedule initialization actions that require the whole document to have been built. Remember that the content of