Another RayJune

Eloquent JavaScript 小记

Just for self review.
The (*) section means it’s important.

Preface

This book intends to make you familiar enough with Javascript to be able to make a computer do what you want.

Why language matters

A good programming language helps the programmer by allowing them to talk about the actions that the computer has to perform on a higher level.

It helps omit uninteresting details, provides convenient building blocks (such as while and console.log), allows you to define your own building blocks (such as sum and range), and makes those blocks easy to compose.

What is JavaScript?

JavaScript is ridiculously liberal in what it allows. The idea behind this design was that it would make programming in JavaScript easier for beginners. In actuality, it mostly makes finding problems in your programs harder because the system will not point them out to you.

This flexibility also has its advantages, though. It leaves space for a lot of techniques that are impossible in more rigid languages, and as you will see (for example in Chapter 10) it can be used to overcome some of JavaScript’s shortcomings.

Code, and what to do with it

In my experience, reading code and writing code are indispensable parts of learning to program, so try to not just glance over the examples. Don’t assume you understand them until you’ve actually written a working solution.

Values, Types, and Operators

Numbers

fractional digital numbers

Calculations with whole numbers (also called integers) smaller than the aforementioned 9 quadrillion are guaranteed to always be precise. Unfortunately, calculations with fractional numbers are generally not.

The important thing is to be aware of it and treat fractional digital numbers as approximations, not as precise values.

such as:

1
2
1.222 - 1.111
// 0.11099999999999999

Special numbers

There are three special values in JavaScript that are considered numbers but don’t behave like normal numbers.

The result two are Infinity and -Infinity.

Infinity - 1 is still Infinity, and so on. Don’t put too much trust in infinity-based computation. It isn’t mathematically solid, and it will quickly lead to our next special number: NaN.

NaN stands for “not a number”, even though it is a value of the number type.

1
console.log(typeof NaN); // number

You’ll get this result when you, for example, try to calculate 0 / 0 (zero divided by zero), Infinity - Infinity, or any number of other numeric operations that don’t yield a precise, meaningful result.

1
2
console.log(typeof NaN); // number
console.log(typeof Infinity); // number

Strings

Strings cannot be divided, multiplied, or subtracted, but the + operator can be used on them. It does not add, but it concatenates—it glues two strings together. The following line will produce the string “concatenate”:

1
'con' + 'cat' + 'e' + 'nate';

Unary operators

Not all operators are symbols. Some are written as words. One example is the typeof operator:

1
2
console.log(typeof 4.5) //number
console.log(typeof "x") // string

The other operators we saw all operated on two values, but typeof takes only one. Operators that use two values are called binary operators, while those that take one are called unary operators.

The minus operator can be used both as a binary operator and as a unary operator.

1
console.log(- (10 - 2)) // → -8

and ! to negate logically

1
console.log(!!true) // → true

Comparisons

There is only one value in JavaScript that is not equal to itself, and that is NaN, which stands for “not a number”.

1
console.log(NaN == NaN) // false

NaN is supposed to denote the result of a nonsensical computation, and as such, it isn’t equal to the result of any other nonsensical computations.

Automatic type conversion

JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions:

1
2
3
4
5
console.log(8 * null) // 0 because null -> 0
console.log('5' - 1) //4
console.log('5' + 1) // 51
console.log("five" * 2) //NaN
console.log(false == 0) //false

When an operator is applied to the “wrong” type of value, JavaScript will quietly convert that value to the type it wants, using a set of rules that often aren’t what you want or expect. This is called type coercion.

So the null in the first expression becomes 0

When something that doesn’t map to a number in an obvious way (such as "five" or undefined) is converted to a number, the value NaN is produced.

When comparing values of the same type using ==, the outcome is easy to predict: you should get true when both values are the same, except in the case of NaN. But when the types differ, JavaScript uses a complicated and confusing set of rules to determine what to do.

1
2
console.log(null == undefined); // → true
console.log(null == 0); // → false

When you want to test whether a value has a real value instead of null or undefined, you can simply compare it to null with the == (or !=) operator.

Boolean: value false

  • 0
  • undefined
  • null
  • NaN
  • empty string (“”)
  • false

Program Structure

The environment

The collection of variables and their values that exist at a given time is called the environment.

When a program starts up, this environment is not empty. It always contains variables that are part of the language standard, and most of the time, it has variables that provide ways to interact with the surrounding system.

For example, in a browser, there are variables and functions to inspect and in influence the currently loaded website and to read mouse and keyboard input.

Summary

You now know that a program is built out of statements, which them- selves sometimes contain more statements. Statements tend to contain expressions, which themselves can be built out of smaller expressions.

Putting statements after one another gives you a program that is executed from top to bottom. You can introduce disturbances in the flow of control by using conditional (if, else, and switch) and looping (while, do, and for) statements.

Variables can be used to file pieces of data under a name, and they are useful for tracking state in your program. The environment is the set of variables that are defined. JavaScript systems always put a number of useful standard variables into your environment.

Functions are special values that encapsulate a piece of program. You can invoke them by writing functionName(argument1, argument2). Such a function call is an expression, and may produce a value.

Exercises

Looping a triangle

my solution (maybe better)

1
2
3
4
5
6
7
var i = 0;
var shap = '#';
while (i < 7) {
console.log(shap);
shap += '#';
i++;
}

book’s standard solution (readability worse)

1
2
for (var line = "#"; line.length < 8; line += "#")
console.log(line);

FizzBuzz

my solution (a little redundancy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (var i = 0; i < 100; i++) {
if (i % 3 === 0) {
if (i % 5 === 0) {
console.log('FizzBuzz');
} else {
console.log('Fizz');
}
} else if (i % 5 === 0) {
if (i % 3 === 0) {
console.log('FizzBuzz');
} else {
console.log('Buzz');
}
} else {
console.log(i);
}
}

book’s standard solution (better choice)

1
2
3
4
5
6
7
8
for (var n = 1; n <= 100; n++) {
var output = "";
if (n % 3 == 0)
output += "Fizz";
if (n % 5 == 0)
output += "Buzz";
console.log(output || n);
}

Chess board

my solution (maybe better)

1
2
3
4
5
6
7
for (var i = 1; i < 9; i++) {
if (i % 2) {
console.log(' # # # #');
} else {
console.log('# # # #');
}
}

book’s standard solution (too complex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var size = 8;
var board = "";
for (var y = 0; y < size; y++) {
for (var x = 0; x < size; x++) {
if ((x + y) % 2 == 0)
board += " ";
else
board += "#";
}
board += "\n";
}
console.log(board);

Functions

Function hoisting

1
2
3
4
console.log("The future says:", future());
function future() {
return "We STILL have no flying cars.";
}

This code works. They are conceptually moved to the top of their scope and can be used by all the code in that scope.

This is sometimes useful because it gives us the freedom to order code in a way that seems meaningful, without worrying about having to define all functions above their first use.

What happens when you put such a function definition inside a conditional (if) block or a loop? Well, don’t do that. Different JavaScript platforms in different browsers have traditionally done different things in that situation, and the latest standard actually forbids it.

1
2
3
4
5
6
function example() {
function a() {} // Okay
if (something) {
function b() {} // Danger!
}
}

(*)The call stack

It will be helpful to take a closer look at the way control flows through functions:

1
2
3
4
5
function greet(who) {
console.log("Hello " + who);
}
greet("Harry");
console.log("Bye");

A run through this program goes roughly like this:

  1. the call to greet causes control to jump to the start of that function (line 2).
  2. It calls console.log (a built-in browser function), which takes control, does its job, and then returns control to line 2.
  3. Then it reaches the end of the greet function, so it returns to the place that called it, at line 4.
  4. The line after that calls console.log again.

We could show the flow of control schematically like this:

1
2
3
4
5
6
7
top
greet
console.log
greet
top
console.log
top

Because a function has to jump back to the place of the call when it returns, the computer must remember the context from which the function was called. In one case, console.log has to jump back to the greet function. In the other case, it jumps back to the end of the program.

The place where the computer stores this context is the call stack.

Every time a function is called, the current context is put on top of this “stack”. When the function returns, it removes the top context from the stack and uses it to continue execution.

Storing this stack requires space in the computer’s memory. When the stack grows too big, the computer will fail with a message like “out of stack space” or “too much recursion”. The following code illustrates this by asking the computer a really hard question, which causes an infinite back-and-forth between two functions.

1
2
3
4
5
6
7
function chicken() {
return egg();
}
function egg() {
return chicken();
}
console.log(chicken() + " came first."); // → ??

Optional Arguments

1
alert("Hello", "Good Evening", "How do you do?");

The function alert officially accepts only one argument. Yet when you call it like this, it doesn’t complain. It simply ignores the other arguments and shows you “Hello”.

JavaScript is extremely broad-minded about the number of arguments you pass to a function If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters simply get assigned the value undefined.

The downside of this is that it is possible—likely, even—that you’ll accidentally pass the wrong number of arguments to functions and no one will tell you about it.

The upside is that this behavior can be used to have a function take “optional” arguments.

1
2
3
4
5
6
7
8
9
10
11
12
function power(base, exponent) {
if (exponent === undefined) {
exponent = 2;
}
var result = 1;
for (var count = 0; count < exponent; count++) {
result *= base;
return result;
}
}
console.log(power(4)); // → 16
console.log(power(4, 3)); // → 64

For example, console.log makes use of this—it outputs all of the values it is given.

1
console.log("R", 2, "D", 2); //→ R2D2

(*)Closure

The ability to treat functions as values, combined with the fact that local variables are “re-created” every time a function is called, brings up an interesting question. What happens to local variables when the function call that created them is no longer active?

1
2
3
4
5
6
7
8
9
10
11
function wrapValue(n) {
var localVariable = n;
return function() {
return localVariable;
};
}
var wrap1 = wrapValue(1);
var wrap2 = wrapValue(2);
console.log(wrap1()); // → 1
console.log(wrap2()); // → 2

This is allowed and works as you’d hope—the variable can still be accessed. In fact, multiple instances of the variable can be alive at the same time, which is another good illustration of the concept that local variables really are re-created for every call.

This feature being able to reference a specific instance of local variables in an enclosing function—is called closure. A function that “closes over” some local variables is called a closure.

With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount.

1
2
3
4
5
6
7
8
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
var twice = multiplier(2);
console.log(twice(5)); // → 10

Thinking about programs like this takes some practice. A good mental model is to think of the function keyword as “freezing” the code in its body and wrapping it into a package (the function value). So when you read return function(…){…}, think of it as returning a handle to a piece of computation, frozen for later use.

Recursion

A function that calls itself is called recursive.

1
2
3
4
5
6
7
8
function power(base, exponent) {
if (exponent == 0) {
return 1;
} else {
return base * power(base, exponent - 1);
}
}
console.log(power(2, 3)); // → 8

This is rather close to the way mathematicians define exponentiation and arguably describes the concept in a more elegant way than the looping variant does.

But this implementation has one important problem: in typical JavaScript implementations, it’s about 10 times slower than the looping version. Running through a simple loop is a lot cheaper than calling a function multiple times.

The dilemma of speed versus elegance is an interesting one. You can see it as a kind of continuum between human-friendliness and machine- friendliness. Almost any program can be made faster by making it bigger and more convoluted. The programmer must decide on an appropriate balance.

efficiency

In the case of the earlier power function, the inelegant (looping) version is still fairly simple and easy to read. It doesn’t make much sense to replace it with the recursive version.

Often, though, a program deals with such complex concepts that giving up some efficiency in order to make the program more straightforward becomes an attractive choice.

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. If it is, find out which parts are taking up the most time, and start exchanging elegance for efficiency in those parts.

Of course, this rule doesn’t mean one should start ignoring performance altogether. In many cases, like the power function, not much simplicity is gained from the “elegant” approach. And sometimes an experienced programmer can see right away that a simple approach is never going to be fast enough.

The reason I’m stressing this is that surprisingly many beginning programmers focus fanatically on efficiency, even in the smallest details. The result is bigger, more complicated, and often less correct programs, that take longer to write than their more straightforward equivalents and that usually run only marginally faster.

But recursion is not always just a less-efficient alternative to loop- ing. Some problems are much easier to solve with recursion than with loops. Most often these are problems that require exploring or processing several “branches”, each of which might branch out again into more branches.

Consider this puzzle: by starting from the number 1 and repeatedly either adding 5 or multiplying by 3, an infinite amount of new numbers can be produced. How would you write a function that, given a number, tries to find a sequence of such additions and multiplications that produce that number? For example, the number 13 could be reached by first multiplying by 3 and then adding 5 twice, whereas the number 15 cannot be reached at all.
Here is a recursive solution:

RayJune: How elegant it is~!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function findSolution(target) {
function find(current, history) {
if (current == target) {
return history;
} else if (current > target) {
return null;
} else {
return find(current + 5, '(' + history + ' + 5)') || find(current * 3, '(' + history + ' * 3)');
}
}
return find(1, '1');
}
console.log(findSolution(24)); //→ (((1 * 3) + 5) * 3)

Growing functions

There are two more or less natural ways for functions to be introduced into programs.

The first is that you find yourself writing very similar code multiple times.

The second way is that you find you need some functionality that you haven’t written yet and that sounds like it deserves its own function. .

How difficult it is to find a good name for a function is a good indication of how clear a concept it is that you’re trying to wrap. Let’s go through an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
function printFarmInventory(cows, chickens) {
var cowString = String(cows);
while (cowString.length < 3) {
cowString = '0' + cowString;
}
console.log(cowString + ' Cows');
var chickenString = String(chickens);
while (chickenString.length < 3) {
chickenString = '0' + chickenString;
}
console.log(chickenString + ' Chickens');
}
printFarmInventory(7, 11);

Mission accomplished! But just as we are about to send the farmer the code (along with a hefty invoice, of course), he calls and tells us he’s also started keeping pigs, and couldn’t we please extend the software to also print pigs?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function printZeroPaddedWithLabel(number, label) {
var numberString = String(number);
while (numberString.length < 3) {
numberString = '0' + numberString;
}
console.log(numberString + ' ' + label);
}
function printFarmInventory(cows, chickens, pigs) {
printZeroPaddedWithLabel(cows, 'Cows');
printZeroPaddedWithLabel(chickens, 'Chickens');
printZeroPaddedWithLabel(pigs, 'Pigs');
}
printFarmInventory(7, 11, 3);

It works! But that name, printZeroPaddedWithLabel, is a little awkward.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function zeroPad(number, width) {
var string = String(number);
while (string.length < width) {
string = '0' + string;
}
return string;
}
function printFarmInventory(cows, chickens, pigs) {
console.log(zeroPad(cows, 3) + ' Cows');
console.log(zeroPad(chickens, 3) + ' Chickens');
console.log(zeroPad(pigs, 3) + ' Pigs');
}
printFarmInventory(7, 16, 3);

A function with a nice, obvious name like zeroPad makes it easier for someone who reads the code to figure out what it does. And it is useful in more situations than just this specific program. For example, you could use it to help print nicely aligned tables of numbers.

How smart and versatile should our function be?

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

Summary

A key aspect in understanding functions is understanding local scopes. Parameters and variables declared inside a function are local to the function, re-created every time the function is called, and not visible from the outside. Functions declared inside another function have access to the outer function’s local scope.

Separating the tasks your program performs into different functions is helpful. You won’t have to repeat yourself as much, and functions can make a program more readable by grouping code into conceptual chunks, in the same way that chapters and sections help organize regular text.

Exercises

Minimum

my solution (maybe better)

1
2
3
4
5
function min(num1, num2) {
return num1 < num2 ? num1 : num2;
}
min(42, 24); // 24

book’s standard solution

1
2
3
4
5
6
7
8
9
10
11
function min(a, b) {
if (a < b)
return a;
else
return b;
}
console.log(min(0, 10));
// → 0
console.log(min(0, -10));
// → -10

Recursion

my solution (almost as same as the standard solution, all good)

1
2
3
4
5
6
7
8
9
10
11
12
13
function isEven(num) {
if (num === 0) {
return false;
} else if (num === 1) {
return true;
} else if (num < 0) {
return void 0;
}
return isEven(num - 2);
}
isEven(3); // true
isEven(8); // false

book’s standard solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function isEven(n) {
if (n == 0)
return true;
else if (n == 1)
return false;
else if (n < 0)
return isEven(-n);
else
return isEven(n - 2);
}
console.log(isEven(50));
// → true
console.log(isEven(75));
// → false
console.log(isEven(-1));
// → false

Bean counting

my solution (forget the essence of the subject, not correct)

1
2
3
4
5
6
7
8
9
10
11
12
function countBs(str) {
var i;
var times = 0;
for (i = 0; i < str.length; i++) {
if (str[i] === 'B') {
times++;
}
}
return times;
}
countBs('B11111B'); // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function countChar(str, letter) {
var i;
var times = 0;
if (!letter) {
letter = 'B';
}
for (i = 0; i < str.length; i++) {
if (str[i] === letter) {
times++;
}
}
return times;
}
countChar('BCBCBC','C'); // 3
countChar('BCBCC',); // 2

book’s standard solution (better)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function countChar(string, ch) {
var counted = 0;
for (var i = 0; i < string.length; i++)
if (string.charAt(i) == ch) {
counted += 1;
}
return counted;
}
function countBs(string) {
return countChar(string, "B");
}
console.log(countBs("BBC"));
// → 2
console.log(countChar("kakkerlak", "k"));
// → 4

Data Structures: Objects and Arrays

delete operator & in operator

1
2
3
4
5
var a = {a: 1};
console.log(a); // {a: 1};
delete a.a;
console.log(a); // {};

The in operator returns true if the specified property is in the specified object or its prototype chain.

The same keyword can also be used in a for loop (for (var name in object)) to loop over an object’s properties.

Mutability

The types of values discussed in earlier chapters, such as numbers, strings, and Booleans, are all immutable—it is impossible to change an existing value of those types.

With objects, on the other hand, the content of a value can be modified by changing its properties.

With objects, there is a difference between having two reference to the same object and having two different objects that contain the same properties. Consider the following code:

1
2
3
4
5
6
7
8
var object1 = {value: 10};
var object2 = object1;
var object3 = {value: 10};
console.log(object1 == object2); // → true
console.log(object1 == object3); // → false
object1.value = 15;
console.log(object2.value); // → 15
console.log(object3.value); // → 10

JavaScript’s == operator, when comparing objects, will return true only if both objects are precisely the same value. Comparing different objects will return false, even if they have identical contents.

There is no “deep” comparison operation built into JavaScript, which looks at object’s con-tents, but it is possible to write it yourself (which will be one of the exercises at the end of this chapter).

The global object

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
var myVar = 10;
console.log("myVar" in window); // → true
console.log(window.myVar); // → 10

Summary

Objects and arrays (which are a specific kind of object) provide ways to group several values into a single value.

Conceptually, this allows us to put a bunch of related things in a bag and run around with the bag.

Objects can also serve as maps, associating values with names. The in operator can be used to find out whether an object contains a property with a given name. The same keyword can also be used in a for loop (for (var name in object)) to loop over an object’s properties.

Exercises

The sum of a range

my solution (all good)

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
32
33
34
35
36
function range(start, end, step) {
var arr = [];
var i = start;
if (!step) {
step = 1;
}
if (step > 0) {
for (; i <= end; i += step) {
arr.push(i);
}
} else {
for (; i >= end; i += step) {
arr.push(i);
}
}
return arr;
}
function sum(arr) {
var result = 0;
var i;
for (i = 0; i < arr.length; i++) {
result += arr[i];
}
return result;
}
console.log(range(1, 10))
// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(range(5, 2, -1));
// → [5, 4, 3, 2]
console.log(sum(range(1, 10)));

book’s standard solution (almost same as mine)

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
function range(start, end, step) {
if (step == null) step = 1;
var array = [];
if (step > 0) {
for (var i = start; i <= end; i += step)
array.push(i);
} else {
for (var i = start; i >= end; i += step)
array.push(i);
}
return array;
}
function sum(array) {
var total = 0;
for (var i = 0; i < array.length; i++)
total += array[i];
return total;
}
console.log(range(1, 10))
// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(range(5, 2, -1));
// → [5, 4, 3, 2]
console.log(sum(range(1, 10)));
// → 55

Reversing an array

my solution (use Array.reverse)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function reverseArray(arr) {
var i = arr.length - 1;
var reverseedArr = [];
while(i >= 0) {
reverseedArr.push(arr[i]);
i--;
}
return reverseedArr;
}
function reverseArrayInPlace(arr) {
arr = arr.reverse();
}
console.log(reverseArray(["A", "B", "C"]));
// → ["C", "B", "A"];
var arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue);
console.log(arrayValue);
// → [5, 4, 3, 2, 1]

book’s standard solution (very awesome)

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
function reverseArray(arr) {
var output = [];
var i;
for (i = array.length - 1; i >= 0; i--) {
output.push(arr[i]);
}
return output;
}
function reverseArrayInPlace(arr) {
var i;
var old;
var len = arr.length;
for (i = 0; i < Math.floor(len / 2); i++) {
old = arr[i];
arr[i] = arr[len- 1 - i];
arr[len - 1 - i] = old;
}
return arr;
}
console.log(reverseArray(["A", "B", "C"]));
// → ["C", "B", "A"];
var arrayValue = [1, 2, 3, 4, 5];
reverseArrayInPlace(arrayValue);
console.log(arrayValue);
// → [5, 4, 3, 2, 1]

A list

not accomplish

1
2
3
4
5
arrayToList
listToArray
prepend

book’s standard solution (very very awesome)

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function arrayToList(array) {
var list = null; // first list-item's reset points to null
var i;
for (i = array.length - 1; i >= 0; i--) {
list = {
value: array[i],
rest: list
};
}
return list;
}
function listToArray(list) {
var arr = [];
var node = list;
while (node) {
arr.push(node.value);
node = node.rest;
}
return arr;
}
function prepend(value, list) {
return {
value: value,
reset: list
};
}
function nth(list, n) {
if (!list) {
return void 0;
} else if (n === 0) {
return list.value;
}
return nth(list.rest, n - 1);
}
console.log(arrayToList([10, 20]));
// → {value: 10, rest: {value: 20, rest: null}}
console.log(listToArray(arrayToList([10, 20, 30])));
// → [10, 20, 30]
console.log(prepend(10, prepend(20, null)));
// → {value: 10, rest:{value: 20, rest: null}}
console.log(nth(arrayToList([10, 20, 30]), 1));
// → 20

Deep comparison

my solution (incomprehensive, not good)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function deepEqual(para1, para2) {
if (para1 === para2) {
return true;
}
if (typeof para1 === 'object' && typeof para2 === 'object') {
for (pro in para1) {
if (pro in para2) {
return false;
}
}
return true;
}
return false;
}

book’s standard solution (perfect)

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
32
33
34
35
36
function deepEqual(a, b) {
var propsInA = 0;
var propsInB = 0;
var prop;
if (a === b) {
return true;
}
if (a == null || typeof a != "object" || b == null || typeof b !="object") {
return false;
}
/* attention: the for...in will also iterate over all enumerable
properties from it's constructor's prototype, in normal case
we will use Object.prototype.hasOwnProperty.call(a, prop); to
get rid of those properties
*/
for (prop in a) {
propsInA += 1;
}
for (prop in b) {
propsInB += 1;
if (!(prop in a) || !deepEqual(a[prop], b[prop])) {
return false;
}
}
return propsInA == propsInB;
}
var obj = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
// → true

Higher-Order Functions

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.

C.A.R. Hoare, 1980 ACM Turing Award Lecture

Let’s briefly go back to the final two example programs in the introduction. The first is self-contained and six lines long.

1
2
3
4
5
6
7
8
var total = 0;
var count = 1;
while (count <= 10) {
total += count;
count += 1;
}
console.log(total);

The second relies on two external functions and is one line long.

1
console.log(sum(range(1, 10)));

Which one is more likely to contain a bug?

If we count the size of the definitions of sum and range, the second
program is also big—even bigger than the first. But still, I’d argue that it is more likely to be correct.

It is more likely to be correct because the solution is expressed in a vocabulary that corresponds to the problem being solved. Summing a range of numbers isn’t about loops and counters. It is about ranges and sums.

The definitions of this vocabulary (the functions sum and range) will still involve loops, counters, and other incidental details. But because they are expressing simpler concepts than the program as a whole, they are easier to get right.

Abstraction

Abstractions hide details and give us the ability to talk about problems at a higher (or more abstract) level.

For a programmer, to notice when a concept is begging to be abstracted into a new word.

Abstracting array traversal (forEach)

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
var array = [1, 2, 3];
for (var i = 0; i < array.length; i++) {
var current = array[i];
console.log(current);
}
function logEach(array) {
for (var i = 0; i < array.length; i++) {
console.log(array[i]);
}
}
function forEach(array, action) {
for (var i = 0; i < array.length; i++) {
action(array[i]);
}
}
forEach(['Wampeter', 'Foma', 'Granfalloon'], console.log);
// → Wampeter
// → Foma
// → Granfalloon
var numbers = [1, 2, 3, 4, 5];
var sum = 0;
forEach(numbers, function (number) {
sum += number;
});
console.log(sum); // → 15

In fact, we don’t need to write forEach ourselves. It is available as a standard method on arrays. Since the array is already provided as the thing the method acts on, forEach takes only one required argument: the function to be executed for each element.

Working with forEach makes it slightly shorter and quite a bit cleaner.

1
2
3
4
5
6
7
8
9
10
function gatherCorrelations(journal) {
var phis = {};
journal.forEach(function(entry) {
entry.events.forEach(function(event) {
if (!(event in phis)) {
phis[event] = phi(tableFor(event , journal));
}});
});
return phis;
}

Higher-order functions

Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions.

Passing along arguments

The noisy function defined earlier, which wraps its argument in another function, has a rather serious deficit.

1
2
3
4
5
6
7
8
function noisy(f) {
return function(arg) {
console.log("calling with", arg);
var val = f(arg);
console.log("called with", arg, "- got", val);
return val;
};
}

For these kinds of situations, JavaScript functions have an apply method.

1
2
3
4
5
function transparentWrapping(f) {
return function () {
return f.apply(null, arguments);
};
}

JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[
{
"name": "Emma de Milliano", "sex": "f",
"born": 1876, "died": 1956,
"father": "Petrus de Milliano",
"mother": "Sophia van Damme"
},
{
"name": "Carolus Haverbeke",
"sex": "m",
"born": 1832,
"died": 1905,
"father": "Carel Haverbeke",
"mother": "Maria van Brussel"
}
]

This format is called JSON, which stands for JavaScript Object Notation. It is widely used as a data storage and communication format on the Web.

JSON is similar to JavaScript’s way of writing arrays and objects, with a few restrictions. All property names have to be surrounded by double quotes, and only simple data expressions are allowed ,no function calls, variables, or anything that involves actual computation. Comments are not allowed in JSON.

JavaScript gives us functions, JSON.stringify and JSON.parse.

1
2
3
4
5
6
7
8
var string = JSON.stringify({
name: "X",
born: 1980
});
console.log(string);
// → {"name":"X","born":1980}
console.log(JSON.parse(string).born);
// → 1980

Methods

Filtering an array

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

Transforming with map

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

Summarizing with reduce

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

Composability

Higher-order functions start to shine when you need to compose functions. As an example, let’s write code that finds the average age for men and for women in the data set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function average(array) {
function plus(a, b) {
return a + b;
}
return array.reduce(plus) / array.length;
}
function age(p) {
return p.died - p.born;
}
function male(p) {
return p.sex == "m";
}
function female(p) {
return p.sex == "f";
}
console.log(average(ancestry.filter(male).map(age))); // → 61.67
console.log(average(ancestry.filter(female).map(age))); // → 54.56

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

The cost

In the happy land of elegant code and pretty rainbows, there lives a spoil-sport monster called inefficiency.

A program that processes an array is most elegantly expressed as a sequence of cleanly separated steps that each do something with the array and produce a new array. But building up all those intermediate arrays is somewhat expensive.

Likewise, passing a function to forEach and letting that method handle the array iteration for us is convenient and easy to read. But function calls in JavaScript are costly compared to simple loop bodies.

And so it goes with a lot of techniques that help improve the clarity of a program. Abstractions add layers between the raw things the computer is doing and the concepts we are working with and thus cause the machine to perform more work.

In JavaScript, an experienced programmer can find ways to write abstract code that is still fast. But it is a problem that comes up a lot.

Fortunately, most computers are insanely fast. If you are processing a modest set of data or doing something that has to happen only on a human time scale (say, every time the user clicks a button), then it does not matter whether you wrote a pretty solution that takes half a millisecond or a super-optimized solution that takes a tenth of a millisecond.

It is helpful to roughly keep track of how often a piece of your program is going to run. If you have a loop inside a loop (either directly or through the outer loop calling a function that ends up performing the inner loop), the code inside the inner loop will end up running N×M times, where N is the number of times the outer loop repeats and M is the number of times the inner loop repeats within each iteration of the outer loop.

This can add up to large numbers, and when a program is slow, the problem can often be traced to only a small part of the code, which sits inside an inner loop.

Summary

Being able to pass function values to other functions is not just a gimmick but a deeply useful aspect of JavaScript.

Arrays provide a number of useful higher-order methods.

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.

Exercises

Flattening

my solution (both good)

1
2
3
4
5
6
7
8
9
function flatten(arr) {
return arr.reduce(function handleFlatten(flattenArr, item) {
return flattenArr.concat(item);
}, []);
}
// testing
var array = [[1, 2, 3], [4, 5], [6]];
flatten(array);

book’s standard solution (as same as mine)

1
2
3
4
5
var arrays = [[1, 2, 3], [4, 5], [6]];
console.log(arrays.reduce(function(flat, current) {
return flat.concat(current);
}, []));

Mother-child age difference

my solution (not understand the subject,unfinishied)

1
2
3
4
5
6
7
8
9
10
11
12
13
function average(array) {
function plus(a, b) {
return a + b;
}
return array.reduce(plus) / array.length;
}
var byName = {};
ancestry.forEach(function(person) {
byName[person.name] = person;
});
// my code here.

book’s standard solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function average(array) {
function plus(a, b) { return a + b; }
return array.reduce(plus) / array.length;
}
var byName = {};
ancestry.forEach(function(person) {
byName[person.name] = person;
});
// code
var differences = ancestry.filter(function(person) {
return byName[person.mother] != null;
}).map(function(person) {
return person.born - byName[person.mother].born;
});
console.log(average(differences));
// → 31.2

Historical life expectancy

my solution (not understand the subject, unfinishied)

1
2
3
4
5
6
function average(arr) {
function plus(a, b) {
return a + b;
}
return arr.reduce(plus) / arr.length;
}

book’s standard solution

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
32
33
34
35
36
37
38
39
40
41
42
43
function average(arr) {
function plus(a, b) {
return a + b;
}
return arr.reduce(plus) / arr.length;
}
// code
function groupBy(arr, groupOf) {
var groups = {};
var groupName;
arr.forEach(function(element) {
groupName = groupOf(element);
if (groupName in groups) {
groups[groupName].push(element);
} else {
groups[groupName] = [element];
}
});
return groups;
}
var byCentury = groupBy(ancestry, function(person) {
return Math.ceil(person.died / 100);
});
for (var century in byCentury) {
var ages = byCentury[century].map(function(person) {
return person.died - person.born;
});
console.log(century + ": " + average(ages));
}
// → 16: 43.5
// 17: 51.2
// 18: 52.8
// 19: 54.8
// 20: 84.7
// 21: 94

Every and then some

my solution (not consider forEach receive a function, so return false can't end the outer every function)

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
function every(arr, func) {
arr.forEach(function (item) {
if (!func(item)) {
return false;
}
})
return true;
}
function some(arr, func) {
arr.forEach(function (item) {
if (func(item)) {
return true;
}
});
return false;
}
// testing
console.log(every([NaN, NaN, NaN], isNaN));
// → true
console.log(every([NaN, NaN, 4], isNaN));
// → false
console.log(some([NaN, 3, 4], isNaN));
// → true
console.log(some([2, 3, 4], isNaN));
// → false

book’s standard solution (correct answer)

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
function every(arr, predicate) {
for (var i = 0; i < arr.length; i++) {
if (!predicate(arr[i]))
return false;
}
return true;
}
function some(arr, predicate) {
for (var i = 0; i < arr.length; i++) {
if (predicate(arr[i]))
return true;
}
return false;
}
console.log(every([NaN, NaN, NaN], isNaN));
// → true
console.log(every([NaN, NaN, 4], isNaN));
// → false
console.log(some([NaN, 3, 4], isNaN));
// → true
console.log(some([2, 3, 4], isNaN));
// → false

The Secret Life of Objects

History

The idea is that the interface is relatively simple and all the complex things going on inside the object can be ignored when working with it.

There are several useful concepts, most importantly that of encapsulation (distinguishing between internal complexity and external interface), that the object-oriented culture has popularized. These are worth studying.

This chapter describes JavaScript’s rather eccentric take on objects and the way they relate to some classical object-oriented techniques.

Methods

Methods are simply properties that hold function values. This is a simple method:

1
2
3
4
5
6
7
8
var rabbit = {};
rabbit.speak = function(line) {
console.log("The rabbit says '" + line + "'");
};
rabbit.speak("I'm alive.");
// → The rabbit says 'I'm alive.'

Prototypes

1
2
3
var empty = {};
console.log(empty.toString); // → function toString ()...{}
console.log(empty.toString()); // → [object Object]

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
6
console.log(Object.getPrototypeOf({}) == Object.prototype);
// → true
console.log(Object.getPrototypeOf(Object.prototype));
// → null
console.log(Object.prototype.__proto__)
// null

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.

1
2
3
4
console.log(Object.getPrototypeOf(isNaN) == Function.prototype);
// → true
console.log(Object.getPrototypeOf([]) == Array.prototype);
// → true

Such a prototype object will itself have a prototype, often Object.prototype , so that it still indirectly provides methods like toString.

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 instance of its constructor.

Here is a simple constructor for rabbits. It is a convention to capitalize the names of constructors so that they are easily distinguished from other functions.

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:

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

Overriding derived properties

1
2
3
4
5
6
7
8
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

Overriding properties that exist in a prototype is often a useful thing to do.

As the rabbit teeth example shows, it can be used to express exceptional properties in instances of a more generic class of objects, while letting the nonexceptional objects simply take a standard value from their prototype.

1
2
3
4
5
6
console.log(Array.prototype.toString == Object.prototype.toString);
// → false
console.log([1, 2].toString()); // → 1,2
console.log(Object.prototype.toString.call([1, 2]));
// → [object Array]

Prototype interference

All properties that we create by simply assigning to them are enumerble. 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 defined our own nonenumerable properties by using the Object.defineProperty function, which allows us to control the type of property we are creating.

1
2
3
4
5
6
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

hasOwnProperty tells us whether the object itself has the property, without looking at its prototypes. This is often a more useful piece of information than what the in operator gives us.

When you are worried that someone (some other code you loaded into
your program) might have messed with the base object prototype, I recommend you write your for/in loops like this:

1
2
3
4
5
for (var name in map) {
if (map.hasOwnProperty(name)) {
// ... this is an own property
}
}

Prototype-less objects

we would actually prefer to have objects without prototypes. 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:

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

Polymorphism

When you call the String function, which converts a value to a string, on an object, it will call the toString method on that object to try to create a meaningful string to return.

I mentioned that some of the standard prototypes define their own version of toString so they can create a string that contains more useful information than "[object Object]".

This technique is called polymorphism———though no actual shape-shifting is involved. Polymorphic code can work with values of different shapes, as long as they support the interface it expects.

Getters and setters

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

Inheritance

1
2
3
4
5
6
7
8
9
10
11
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.

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 main reason for this is that it is often confused with polymorphism, sold as a more powerful tool than it really is, and subsequently overused in all kinds of ugly ways.
Whereas encapsulation and polymorphism can be used to separate pieces of code from each other, reducing the tangledness of the overall program, inheritance fundamentally ties types together, creating more tangle.

You can have polymorphism without inheritance, as we saw. I am not going to tell you to avoid inheritance entirely—I use it regularly in my own programs. But you should see it as a slightly dodgy trick that can help you define new types with little code, not as a grand principle of code organization. A preferable way to extend types is through composition, such as how UnderlinedCell builds on another cell object by simply storing it in a property and forwarding method calls to it in its own methods.

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.

Summary

One useful thing to do with objects is to specify an interface for them and tell everybody that they are supposed to talk to your object only through that interface. The rest of the details that make up your object are now encapsulated, hidden behind the interface.

Exercises

A vector type

my solution (犯了一个错误,在 prototype 上加属性的话只能加定值,不能加变量(在这里我的值就是 Math.pow(undefined) -> NaN ),而标准答案写的非常正确)

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
function Vector(x, y) {
this.x = x;
this.y = y;
}
Vector.prototype.plus = function (vector) {
this.x += vector.x;
this.y += vector.y;
return this;
}
/* can also write like this
Vector.prototype.plus = function (vector) {
return new Vector(this.x + vector.x, this.y + vector.y);
}
*/
Vector.prototype.minus = function (vector) {
this.x -= vector.x;
this.y -= vector.y;
return this;
}
Vector.prototype.length = Math.pow((Math.pow(this.x, 2) + Math.pow(this.y, 2)), 0.5);
console.log(new Vector(1, 2).plus(new Vector(2, 3)));
// → Vector{x: 3, y: 5}
console.log(new Vector(1, 2).minus(new Vector(2, 3)));
// → Vector{x: -1, y: -1}
console.log(new Vector(3, 4).length);
// → NaN (not the correct answer -> 5)

book’s standard solution (Object.defineProperty)

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 Vector(x, y) {
this.x = x;
this.y = y;
}
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};
Vector.prototype.minus = function(other) {
return new Vector(this.x - other.x, this.y - other.y);
};
Object.defineProperty(Vector.prototype, "length", {
get: function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
});
console.log(new Vector(1, 2).plus(new Vector(2, 3)));
// → Vector{x: 3, y: 5}
console.log(new Vector(1, 2).minus(new Vector(2, 3)));
// → Vector{x: -1, y: -1}
console.log(new Vector(3, 4).length);
// → 5

Another cell

my solution (not finish, the topic is puzzled)

1
2
3
4
5
6
7
8
9
// Your code here.
var sc = new StretchCell(new TextCell("abc"), 1, 2);
console.log(sc.minWidth());
// → 3
console.log(sc.minHeight());
// → 2
console.log(sc.draw(3, 2));
// → ["abc", " "]

book’s standard solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function StretchCell(inner, width, height) {
this.inner = inner;
this.width = width;
this.height = height;
}
StretchCell.prototype.minWidth = function() {
return Math.max(this.width, this.inner.minWidth());
};
StretchCell.prototype.minHeight = function() {
return Math.max(this.height, this.inner.minHeight());
};
StretchCell.prototype.draw = function(width, height) {
return this.inner.draw(width, height);
};
var sc = new StretchCell(new TextCell("abc"), 1, 2);
console.log(sc.minWidth());
// → 3
console.log(sc.minHeight());
// → 2
console.log(sc.draw(3, 2));
// → ["abc", " "]

Sequence interface

book’s standard solution

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// I am going to use a system where a sequence object has two methods:
//
// * next(), which returns a boolean indicating whether there are more
// elements in the sequence, and moves it forward to the next
// element when there are.
//
// * current(), which returns the current element, and should only be
// called after next() has returned true at least once.
function logFive(sequence) {
for (var i = 0; i < 5; i++) {
if (!sequence.next())
break;
console.log(sequence.current());
}
}
function ArraySeq(array) {
this.pos = -1;
this.array = array;
}
ArraySeq.prototype.next = function() {
if (this.pos >= this.array.length - 1) {
return false;
}
this.pos++;
return true;
};
ArraySeq.prototype.current = function() {
return this.array[this.pos];
};
function RangeSeq(from, to) {
this.pos = from - 1;
this.to = to;
}
RangeSeq.prototype.next = function() {
if (this.pos >= this.to) {
return false;
}
this.pos++;
return true;
};
RangeSeq.prototype.current = function() {
return this.pos;
};
logFive(new ArraySeq([1, 2]));
// → 1
// → 2
logFive(new RangeSeq(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104
// This alternative approach represents the empty sequence as null,
// and gives non-empty sequences two methods:
//
// * head() returns the element at the start of the sequence.
//
// * rest() returns the rest of the sequence, or null if there are no
// elemements left.
//
// Because a JavaScript constructor can not return null, we add a make
// function to constructors of this type of sequence, which constructs
// a sequence, or returns null if the resulting sequence would be
// empty.
function logFive2(sequence) {
for (var i = 0; i < 5 && sequence != null; i++) {
console.log(sequence.head());
sequence = sequence.rest();
}
}
function ArraySeq2(array, offset) {
this.array = array;
this.offset = offset;
}
ArraySeq2.prototype.rest = function() {
return ArraySeq2.make(this.array, this.offset + 1);
};
ArraySeq2.prototype.head = function() {
return this.array[this.offset];
};
ArraySeq2.make = function(array, offset) {
if (offset == null) offset = 0;
if (offset >= array.length) {
return null;
}
else {
return new ArraySeq2(array, offset);
}
};
function RangeSeq2(from, to) {
this.from = from;
this.to = to;
}
RangeSeq2.prototype.rest = function() {
return RangeSeq2.make(this.from + 1, this.to);
};
RangeSeq2.prototype.head = function() {
return this.from;
};
RangeSeq2.make = function(from, to) {
if (from > to) {
return null;
}
else {
return new RangeSeq2(from, to);
}
};
logFive2(ArraySeq2.make([1, 2]));
// → 1
// → 2
logFive2(RangeSeq2.make(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104

Bugs and Error Handling

“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”

—Brian Kernighan and P.J. Plauger, The Elements of Programming Style

Flaws in a program are usually called bugs. Bugs can be programmer errors or problems in other systems that the program interacts with.

Some bugs are immediately apparent, while others are subtle and might remain hidden in a system for years.

Often, problems surface only when a program encounters a situation that the programmer didn’t originally consider.

Sometimes such situations are unavoidable. When the user is asked to input their age and types orange, this puts our program in a difficult position. The situation has to be anticipated and handled somehow.

Strict mode

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
1
2
3
4
5
6
7
"use strict";
function Person(name) {
this.name = name;
}
// Oops , forgot 'new '
var ferdinand = Person("Ferdinand");
// → TypeError: Cannot set property 'name' of undefined

It disallows giving a function multiple parameters with the same name and removes certain problematic language features entirely (such as the with statement, which is so misguided it is not further discussed in this book).

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

Testing

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
function Vector(x, y) {
this.x = x;
this.y = y;
}
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};
function testVector() {
var p1 = new Vector(10, 20);
var p2 = new Vector(-10, 5);
var p3 = p1.plus(p2);
if (p1.x !== 10) {
return "fail: x property";
}
if (p1.y !== 20) {
return "fail: y property";
}
if (p2.x !== -10) {
return "fail: negative x property";
}
if (p3.x !== 0) {
return "fail: x from plus";
}
if (p3.y !== 25) {
return "fail: y from plus";
}
return "everything ok";
}
console.log(testVector()); // → everything ok

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 frame-works.

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.

Putting a few strategic console.log calls into the program is a good way to get additional information about what the program is doing.

Exceptions

When a function cannot proceed normally, what we would like to do is just stop what we are doing and immediately jump back to a place that knows how to handle the problem. This is what exception handling does.

Raising an exception somewhat resembles a super-charged return from a function: it jumps out of not just the current function but also out of its callers, all the way down to the first call that started the current execution. This is called unwinding the stack.

Their power lies in the fact that you can set “obstacles” along the stack to catch the exception as it is zooming down. Then you can do something with it, after which the program continues running at the point where the exception was caught.

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
32
33
34
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';
}
return 'two angry bears';
}
try {
console.log('You see', look());
} catch (error) {
console.log('Something went wrong: ' + error);
}
console.log('the exception don\'t influence normal flow');
// testing
/* input left to console */
// You see a house
// the exception don't influence normal flow
/* input 1 to console */
// Something went wrong: Error: Invalid direction: 1
// the exception don't influence normal flow

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.

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.

Note that the function look completely ignores the possibility that promptDirection might go wrong. This is the big advantage of exceptions—error-handling code is necessary only at the point where the error occurs and at the point where it is handled. The functions in between can forget all about it.

Cleaning up after exceptions

Consider the following situation: After this function finishes, context restores this variable to its old value.

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

What if body raises an exception? In that case, the call to withContext will be thrown off the stack by the exception, and context will never be set back to its old value.

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;
}
}

Note that we no longer have to store the result of body (which we want to return) in a variable. Even if we return directly from the try block, the finally block will be run. Now we can do this and be safe:

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

Note that we no longer have to store the result of body (which we want to return) in a variable. Even if we return directly from the try block, the finally block will be run. Now we can do this and be safe:

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
withContext(5, function() {
if (context < 10) {
throw new Error("Not enough context!");
}
});
} catch (e) {
console.log("Ignoring: " + e);
}
// → Ignoring: Error: Not enough context!
console.log(context);
// → null

Even though the function called from withContext exploded, withContext itself still properly cleaned up the context variable.

Selective catching

For programmer mistakes or problems that the program cannot possibly handle, just letting the error go through is often okay. An unhandled exception is a reasonable way to signal a broken program, and the JavaScript console will, on modern browsers, provide you with some information about which function calls were on the stack when the problem occurred.

For problems that are expected to happen during routine use, crashing with an unhandled exception is not a very friendly response.

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.

But it might not be. Some other assumption might be violated, or you might have introduced a bug somewhere that is causing an exception. Here is an example, which attempts to keep on calling promptDirection until it gets a valid answer:

1
2
3
4
5
6
7
8
9
for (;;) {
try {
var dir = promtDirection("Where?"); // ← typo!
console.log("You chose ", dir);
break;
} catch (e) {
console.log("Not a valid direction. Try again.");
}
}

The for (;;) construct is a way to intentionally create a loop that doesn’t terminate on its own. We break out of the loop only when a valid direction is given. But we misspelled promptDirection, which will result in an “undefined variable” error. Because the catch block completely ignores its exception value (e), assuming it knows what the problem is, it wrongly treats the variable error as indicating bad input. Not only does this cause an infinite loop, but it also “buries” the useful error message about the misspelled variable.

As a general rule, don’t blanket-catch exceptions unless it is for the purpose of “routing” them somewhere—for example, over the network to tell another system that our program crashed. And even then, think carefully about how you might be hiding information.

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. But how do we recognize an exception?

Of course, we could match its message property against the error message we happen to expect. But that’s a shaky way to write code—we’d be using information that’s intended for human consumption (the message) to make a programmatic decision. As soon as someone changes (or translates) the message, the code will stop working.

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";

Now promptDirection can throw such an error.

1
2
3
4
5
6
7
8
9
10
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") {
return "L";
}
if (result.toLowerCase() == "right") {
return "R";
}
throw new InputError("Invalid direction: " + result);
}

And the loop can catch it more carefully.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (;;) {
try {
var dir = promptDirection("Where?");
console.log("You chose ", dir);
break;
} catch (e) {
if (e instanceof InputError) {
console.log("Not a valid direction. Try again.");
}
else {
throw e;
}
}
}

This will catch only instances of InputError and let unrelated exceptions through. If you reintroduce the typo, the undefined variable error will be properly reported.

Assertions

Assertions are a tool to do basic sanity checking for programmer errors. Consider this helper function, assert:

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];
}

This provides a compact way to enforce expectations, helpfully blowing up the program if the stated condition does not hold.

For instance, the lastElement function, which fetches the last element from an array, would return undefined on empty arrays if the assertion was omitted. Fetching the last element from an empty array does not make much sense, so it is almost certainly a programmer error to do so.

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.

Summary

Mistakes and bad input are facts of life. Bugs in programs need to be found and fixed. They can become easier to notice by having automated test suites and adding assertions to your programs.

Problems caused by factors outside the program’s control should usually be handled gracefully. Sometimes, when the problem can be handled locally, special return values are a sane way to track them. Otherwise, exceptions are preferable.

Throwing an exception causes the call stack to be unwound until the next enclosing try/catch block or until the bottom of the stack. The exception value will be given to the catch block that catches it, which should verify that it is actually the expected kind of exception and then do something with it. To deal with the unpredictable control flow caused by exceptions, finally blocks can be used to ensure a piece of code is always run when a block finishes.

Exercises

Retry

1
2
3
4
5
6
7
8
9
10
11
12
for (;;) {
try {
var dir = promptDirection("Where?");
console.log("You chose ", dir);
break;
} catch (e) {
if (e instanceof InputError)
console.log("Not a valid direction. Try again.");
else
throw e;
}
}

The locked box

Regular Expressions

“Some people, when confronted with a problem, think ‘I know, I’ll use regular expressions.’ Now they have two problems.”
——Jamie Zawinski

Yuan-Ma said, ‘When you cut against the grain of the wood, much strength is needed. When you program against the grain of a problem, much code is needed.’
——Master Yuan-Ma, The Book of Programming

Regular expressions are a way to describe patterns in string data. They form a small, separate language that is part of JavaScript and many other languages and tools.

Regular expressions are both terribly awkward and extremely useful. Their syntax is cryptic, and the programming interface JavaScript provides for them is clumsy. But they are a powerful tool for inspecting and processing strings. Properly understanding regular expressions will make you a more effective programmer.

Creating a regular expression

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

Testing for matches

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

A regular expression consisting of only nonspecial characters simply represents that sequence of characters. If abc occurs anywhere in the string we are testing against (not just at the start), test will return true.

1
2
console.log(/abc/.test("deabc"));
// → true

Matching a set of characters

Finding out whether a string contains abc could just as well be done with a call to indexOf. Regular expressions allow us to go beyond that and express more complicated patterns.

Say we want to match any number. 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.

Both of the following expressions match all strings that contain a digit:

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
  • \D A character that is not a digit
  • \w An alphanumeric character (“word character”)
  • \W A nonalphanumeric character
  • \s Any whitespace character (space, tab, newline, and similar)
  • \S A nonwhitespace character
  • . Any character except for newline

So you could match a date and time format like 30-01-2003 15:20 with the following expression:

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

That looks completely awful, doesn’t it? It has way too many backslashes, producing background noise that makes it hard to spot the actual pattern expressed. We’ll see a slightly improved version of this expression later.

These backslash codes can also be used inside square brackets. For example, [\d.] means any digit or a period character. But note that the period itself, when used between square brackets, loses its special meaning. The same goes for other special characters, such as +.

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.

1
2
3
4
5
var notBinary = /[^01]/;
console.log(notBinary.test("1100100010100110"));
// → false
console.log(notBinary.test("1100100010200110"));
// → true

Repeating parts of a pattern

We now know how to match a single digit. What if we want to match a whole number—a sequence of one or more digits?

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
console.log(/'\d+'/.test("'123'"));
// → true
console.log(/'\d+'/.test("''"));
// → false

The star (*) has a similar meaning but also allows the pattern to match zero times.

1
2
3
4
console.log(/'\d*'/.test("'123'"));
// → true
console.log(/'\d*'/.test("''"));
// → true

A question mark makes a part of a pattern “optional”, meaning it may occur zero or one time.

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

To indicate that a pattern should occur a precise number of times, use curly braces. 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.

1
2
3
var dateTime = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/;
console.log(dateTime.test("30-1-2003 8:45"));
// → true

You can also specify open-ended ranges when using curly braces by omitting the number after the comma. So {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 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", index: 8, input: "one two 100"]
console.log(match.index);
// → 8

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. 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"]

Groups can be useful for extracting parts of a string. If we don’t just want to verify whether a string contains a date but also extract it and construct an object that represents it, we can wrap parentheses around the digit patterns and directly pick the date out of the result of exec.

Choice patterns

We could write three regular expressions and test them in turn, but there is a nicer way. 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 replace method

String values have a replace method, which can be used to replace part of the string with another string.

1
2
console.log("papa".replace("p", "m"));
// → mapa

The first argument can also be a regular expression, in which case the first match of the regular expression is replaced. When a g option (for global) is added to the regular expression, all matches in the string will be replaced, not just the first.

1
2
3
4
console.log("Borobudur".replace(/[ou]/, "a"));
// → Barobudur
console.log("Borobudur".replace(/[ou]/g, "a"));
// → Barabadar

The dollar 1 and dollar 2 in the replacement string refer to the parenthesized groups in the pattern. 1 is replaced by the text that matched against the first group, dollar 2 by the second, and so on, up to dollar 9. The whole match can be referred to with $&.

1
2
3
4
5
6
console.log(
"Hopper, Grace\nMcCarthy, John\nRitchie, Dennis"
.replace(/([\w ]+), ([\w ]+)/g, "$2 $1"));
// → Grace Hopper
// John McCarthy
// Dennis Ritchie

It is also possible to pass a function, rather than a string, as the second argument to replace. For each replacement, the function will be called with the matched groups (as well as the whole match) as arguments, and its return value will be inserted into the new string.

1
2
3
4
5
var s = "the cia and fbi";
console.log(s.replace(/\b(fbi|cia)\b/g, function(str) {
return str.toUpperCase();
}));
// → the CIA and FBI

Greed

change + or * to ?

1
2
3
4
5
6
7
function stripComments(code) {
return code.replace(/\/\/.*|\/\*[^]*\*\//g, "");
}
function stripComments(code) {
return code.replace(/\/\/.*|\/\*[^]*?\*\//g, "");
}

A lot of bugs in regular expression programs can be traced to unintentionally using a greedy operator where a nongreedy one would work better. When using a repetition operator, consider the nongreedy variant first.

The search method

The indexOf method on strings cannot be called with a regular expression. But there is another method, search, which does expect a regular expression. Like indexOf, it returns the first index on which the expression was found, or -1 when it wasn’t found.

1
2
3
4
console.log(" word".search(/\S/));
// → 2
console.log(" ".search(/\S/));
// → -1

Unfortunately, there is no way to indicate that the match should start at a given offset (like we can with the second argument to indexOf), which would often be useful.

The lastIndex property

The exec method similarly does not provide a convenient way to start searching from a given position in the string. But it does provide an inconvenient way.

1
2
3
4
5
6
7
var pattern = /y/g;
pattern.lastIndex = 3;
var match = pattern.exec("xyzzy");
console.log(match.index);
// → 4
console.log(pattern.lastIndex);
// → 5

Looping over matches

1
2
3
4
5
6
7
8
var input = "A string with 3 numbers in it... 42 and 88.";
var number = /\b(\d+)\b/g;
var match;
while (match = number.exec(input))
console.log("Found", match[1], "at", match.index);
// → Found 3 at 14
// Found 42 at 33
// Found 88 at 40

Parsing an INI file

searchengine=http://www.google.com/search?q=$1
spitefulness=9.7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function parseINI(string) {
// Start with an object to hold the top-level fields
var currentSection = {name: null, fields: []};
var categories = [currentSection];
string.split(/\r?\n/).forEach(function(line) {
var match;
if (/^\s*(;.*)?$/.test(line)) {
return;
} else if (match = line.match(/^\[(.*)\]$/)) {
currentSection = {name: match[1], fields: []};
categories.push(currentSection);
} else if (match = line.match(/^(\w+)=(.*)$/)) {
currentSection.fields.push({name: match[1],
value: match[2]});
} else {
throw new Error("Line '" + line + "' is invalid.");
}
});
return categories;
}

International characters

Because of JavaScript’s initial simplistic implementation and the fact that this simplistic approach was later set in stone as standard behavior, JavaScript’s regular expressions are rather dumb about characters that do not appear in the English language. For example, as far as JavaScript’s regular expressions are concerned, a “word character” is only one of the 26 characters in the Latin alphabet (uppercase or lowercase) and, for some reason, the underscore character. Things like é or β, which most definitely are word characters, will not match \w (and will match uppercase \W, the nonword category).

By a strange historical accident, \s (whitespace) does not have this problem and matches all characters that the Unicode standard considers whitespace, including things like the nonbreaking space and the Mongolian vowel separator.

Some regular expression implementations in other programming languages have syntax to match specific Unicode character categories, such as “all uppercase letters”, “all punctuation”, or “control characters”. There are plans to add support for such categories to JavaScript, but it unfortunately looks like they won’t be realized in the near future.

Exercises

Regexp golf

Quoting style

Numbers again

Modules

A beginning programmer writes her programs like an ant builds her hill, one piece at a time, without thought for the bigger structure. Her programs will be like loose sand. They may stand for a while, but growing too big they fall apart.

Realizing this problem, the programmer will start to spend a lot of time thinking about structure. Her programs will be rigidly structured, like rock sculptures. They are solid, but when they must change, violence must be done to them.

The master programmer knows when to apply structure and when to leave things in their simple form. Her programs are like clay, solid yet malleable.

——Master Yuan-Ma, The Book of Programming

When looking at a larger program in its entirety, individual functions start to blend into the background. Such a program can be made more readable if we have a larger unit of organization.

Modules divide programs into clusters of code that, by some criterion, belong together. This chapter explores some of the benefits that such division provides and shows techniques for building modules in JavaScript.

Why modules help

Structure helps people who aren’t yet familiar with the code find what they are looking for and makes it easier for the programmer to keep things that are related close together.

As a general rule, structuring things costs energy. In the early stages of a project, when you are not quite sure yet what goes where or what kind of modules the program needs at all, I endorse a minimalist, structureless attitude. Just put everything wherever it is convenient to put it until the code stabilizes. That way, you won’t be wasting time moving pieces of the program back and forth, and you won’t accidentally lock yourself into a structure that does not actually fit your program.

Namespacing

Though JavaScript provides no actual module construct yet, objects can be used to create publicly accessible subnamespaces, and functions can be used to create an isolated, private namespace inside of a module.

Later in this chapter, I will discuss a way to build reasonably convenient, namespace-isolating modules on top of the primitive concepts that JavaScript gives us.

Decoupling

A well-designed module will provide an interface for external code to use. As the module gets updated with bug fixes and new functionality, the existing interface stays the same (it is stable) so that other modules can use the new, improved version without any changes to themselves.

Note that a stable interface does not mean no new functions, methods, or variables are added. It just means that existing functionality isn’t removed and its meaning is not changed.

Using functions as namespaces

1
2
3
4
5
6
7
(function() {
function square(x) { return x * x; }
var hundred = 100;
console.log(square(hundred));
}());
// → 10000

Objects as interfaces

1
2
3
4
5
6
7
8
9
10
11
var weekDay = function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
}();
console.log(weekDay.name(weekDay.number("Sunday")));
// → Sunday

For bigger modules, gathering all the exported values into an object at the end of the function becomes awkward since many of the exported functions are likely to be big and you’d prefer to write them somewhere else, near related internal code. A convenient alternative is to declare an object (conventionally named exports) and add properties to that whenever we are defining something that needs to be exported.

In the following example, the module function takes its interface object as an argument, allowing code outside of the function to create it and store it in a variable. (Outside of a function, this refers to the global scope object.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function(exports) {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};
})(this.weekDay = {});
console.log(weekDay.name(weekDay.number("Saturday")));
// → Saturday

Detaching from the global scope

The previous pattern is commonly used by JavaScript modules intended for the browser. The module will claim a single global variable and wrap its code in a function in order to have its own private namespace. But this pattern still causes problems if multiple modules happen to claim the same name or if you want to load two versions of a module alongside each other.

With a little plumbing, we can create a system that allows one module to directly ask for the interface object of another module, without going through the global scope. Our goal is a require function that, when given a module name, will load that module’s file (from disk or the Web, depending on the platform we are running on) and return the appropriate interface value.

This approach solves the problems mentioned previously and has the added benefit of making your program’s dependencies explicit, making it harder to accidentally make use of some module without stating that you need it.

For require we need two things. First, we want a function readFile, which returns the content of a given file as a string. (A single such function is not present in standard JavaScript, but different JavaScript environments, such as the browser and Node.js, provide their own ways of accessing files. For now, let’s just pretend we have this function.) Second, we need to be able to actually execute this string as JavaScript code.

Evaluating data as code

There are several ways to take data (a string of code) and run it as part of the current program.

The most obvious way is the special operator eval, which will execute a string of code in the current scope. This is usually a bad idea because it breaks some of the sane properties that scopes normally have, such as being isolated from the outside world.

1
2
3
4
5
6
7
function evalAndReturnX(code) {
eval(code);
return x;
}
console.log(evalAndReturnX("var x = 2"));
// → 2

A better way of interpreting data as code is to use the Function constructor. This takes two arguments: a string containing a comma-separated list of argument names and a string containing the function’s body.

1
2
3
var plusOne = new Function("n", "return n + 1;");
console.log(plusOne(4));
// → 5

Require

minimal implemantation

The following is a minimal implementation of require:

1
2
3
4
5
6
7
8
9
function require(name) {
var code = new Function("exports", readFile(name));
var exports = {};
code(exports);
return exports;
}
console.log(require("weekDay").name(1));
// → Monday

Since the new Function constructor wraps the module code in a function, we don’t have to write a wrapping namespace function in the module file itself.
And since we make exports an argument to the module function, the module does not have to declare it. This removes a lot of clutter from our example module.

1
2
3
4
5
6
7
8
9
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};

When using this pattern, a module typically starts with a few variable declarations that load the modules it depends on.

1
2
3
4
var weekDay = require("weekDay");
var today = require("today");
console.log(weekDay.name(today.dayNumber()));
improvement

The simplistic implementation of require given previously has several problems. For one, it will load and run a module every time it is required, so if several modules have the same dependency or a require call is put inside a function that will be called multiple times, time and energy will be wasted.

This can be solved by storing the modules that have already been loaded in an object and simply returning the existing value when one is loaded multiple times.

The second problem is that it is not possible for a module to directly export a value other than the exports object, such as a function. For example, a module might want to export only the constructor of the object type it defines. Right now, it cannot do that because require always uses the exports object it creates as the exported value.

The traditional solution for this is to provide modules with another variable, module, which is an object that has a property exports. This property initially points at the empty object created by require but can be overwritten with another value in order to export something else.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function require(name) {
if (name in require.cache) {
return require.cache[name];
}
var code = new Function("exports, module", readFile(name));
var exports = {};
var module = {exports: exports};
code(exports, module);
require.cache[name] = module.exports;
return module.exports;
}
require.cache = Object.create(null);

We now have a module system that uses a single global variable (require) to allow modules to find and use each other without going through the global scope.

This style of module system is called CommonJS modules, after the pseudo-standard that first specified it. It is built into the Node.js system. Real implementations do a lot more than the example I showed. Most importantly, they have a much more intelligent way of going from a module name to an actual piece of code, allowing both pathnames relative to the current file and module names that point directly to locally installed modules.

Slow-loading modules

Though it is possible to use the CommonJS module style when writing JavaScript for the browser, it is somewhat involved. The reason for this is that reading a file (module) from the Web is a lot slower than reading it from the hard disk. While a script is running in the browser, nothing else can happen to the website on which it runs, for reasons that will become clear in Chapter 14. This means that if every require call went and fetched something from some faraway web server, the page would freeze for a painfully long time while loading its scripts.

One way to work around this problem is to run a program like Browserify on your code before you serve it on a web page. This will look for calls to require, resolve all dependencies, and gather the needed code into a single big file. The website itself can simply load this file to get all the modules it needs.

Another solution is to wrap the code that makes up your module in a function so that the module loader can first load its dependencies in the background and then call the function, initializing the module, when the dependencies have been loaded. That is what the Asynchronous Module Definition (AMD) module system does.

Interface design

Designing interfaces for modules and object types is one of the subtler aspects of programming. Any nontrivial piece of functionality can be modeled in various ways. Finding a way that works well requires insight and foresight.

The best way to learn the value of good interface design is to use lots of interfaces—some good, some bad. Experience will teach you what works and what doesn’t. Never assume that a painful interface is “just the way it is”. Fix it, or wrap it in a new interface that works better for you.

Predictability

If programmers can predict the way your interface works, they (or you) won’t get sidetracked as often by the need to look up how to use it. Thus, try to follow conventions.

When there is another module or part of the standard JavaScript environment that does something similar to what you are implementing, it might be a good idea to make your interface resemble the existing interface. That way, it’ll feel familiar to people who know the existing interface.

Composability

In your interfaces, try to use the simplest data structures possible and make functions do a single, clear thing. Whenever practical, make them pure functions.

Layered interfaces

When designing an interface for a complex piece of functionality—sending email, for example—you often run into a dilemma. On the one hand, you do not want to overload the user of your interface with details. They shouldn’t have to study your interface for 20 minutes before they can send an email. On the other hand, you do not want to hide all the details either—when people need to do complicated things with your module, they should be able to.

Often the solution is to provide two interfaces: a detailed low-level one for complex situations and a simple high-level one for routine use. The second can usually be built easily using the tools provided by the first. In the email module, the high-level interface could just be a function that takes a message, a sender address, and a receiver address and then sends the email. The low-level interface would allow full control over email headers, attachments, HTML mail, and so on.

Summary

Modules provide structure to bigger programs by separating the code into different files and namespaces. Giving these modules well-defined interfaces makes them easier to use and reuse and makes it possible to continue using them as the module itself evolves.

Though the JavaScript language is characteristically unhelpful when it comes to modules, the flexible functions and objects it provides make it possible to define rather nice module systems. Function scopes can be used as internal namespaces for the module, and objects can be used to store sets of exported values.

There are two popular, well-defined approaches to such modules. One is called CommonJS Modules and revolves around a require function that fetches a module by name and returns its interface. The other is called AMD and uses a define function that takes an array of module names and a function and, after loading the modules, runs the function with their interfaces as arguments.

CommonJS and AMD solves the two remaining problems with module pattern: dependency resolution and pollution of global scope: We only need to take care of dependencies for each module or each file. And there is no pollution for global scope.

Exercises

Month names

A return to electronic life

Circular dependencies

JavaScript and the Browser

Networks and the Internet

A network protocol describes a style of communication over a network. There are protocols for sending email, for fetching email, for sharing files, or even for controlling computers that happen to be infected by malicious software.

For example, a simple chat protocol might consist of one computer sending the bits that represent the text “CHAT?” to another machine and the other responding with “OK!” to confirm that it understands the protocol. They can then proceed to send each other strings of text, read the text sent by the other from the network, and display whatever they receive on their screens.

Most protocols are built on top of other protocols. Our example chat protocol treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. Ensuring those things is already a rather difficult technical problem.

The Transmission Control Protocol (TCP) is a protocol that solves this problem. All Internet-connected devices “speak” it, and most communication on the Internet is built on top of it.

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.

Such a connection acts as a two-way pipe through which bits can flow—the machines on both ends can put data into it. Once the bits are successfully transmitted, they can be read out again by the machine on the other side. This is a convenient model. You could say that TCP provides an abstraction of the network.

The Web

The World Wide Web (not to be confused with the Internet as a whole) is a set of protocols and formats that allow us to visit web pages in a browser. The “Web” part in the name refers to the fact that such pages can easily link to each other, thus connecting into a huge mesh that users can move through.

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:

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

If you type the previous URL into your browser’s address bar, it will try to retrieve and display the document at that URL.

  1. First, your browser has to find out what address eloquentjavascript.net refers to.
  2. Then, using the HTTP protocol, it makes a connection to the server at that address and asks for the resource /12_browser.html.

In the sandbox

Running programs downloaded from the Internet is potentially dangerous. You do not know much about the people behind most sites you visit, and they do not necessarily mean well. Running programs by people who do not mean well is how you get your computer infected by viruses, your data stolen, and your accounts hacked.

Isolating a programming environment in this way is called sandboxing, the idea being that the program is harmlessly playing in a sandbox. But you should imagine this particular kind of sandbox as having a cage of thick steel bars over it, which makes it somewhat different from your typical playground sandbox.

The hard part of sandboxing is allowing the programs enough room to be useful yet at the same time restricting them from doing anything dangerous. Lots of useful functionality, such as communicating with other servers or reading the content of the copy-paste clipboard, can also be used to do problematic, privacy-invading things.

The Document Object Model

The browser builds up a model of the document’s structure and then uses this model to draw the page on the screen.

This representation of the document is one of the toys that a JavaScript program has available in its sandbox. You can read from the model and also change it. It acts as a live data structure: when it is modified, the page on the screen is updated to reflect the changes.

Document structure

The global variable document gives us access to these objects. Its documentElement property refers to the object representing thetag. It also provides the properties head and body, which hold the objects for those elements.

Trees

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.documentElement serves as the root.

Trees come up a lot in computer science. In addition to representing recursive structures such as HTML documents or programs, they are often used to maintain sorted sets of data because elements can usually be found or inserted more efficiently in a sorted tree than in a sorted flat array.

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
  • Attribute nodes have the value 2
  • Text nodes have the value 3

The standard

Using cryptic numeric codes to represent node types is not a very JavaScript-like thing to do. Later in this chapter, we’ll see that other parts of the DOM interface also feel cumbersome and alien.

The reason for this is that the DOM wasn’t designed for just JavaScript. Rather, it tries to define a language-neutral interface that can be used in other systems as well—not just HTML but also XML, which is a generic data format with an HTML-like syntax.

As an example of such poor integration, consider the childNodes property that element nodes in the DOM have. This property holds an array-like object, with a length property and properties labeled by numbers to access the child nodes. But it is an instance of the NodeList type, not a real array, so it does not have methods such as slice and forEach.

Moving through the tree

DOM nodes contain a wealth of links to other nearby nodes.

  • parentNode
  • childNodes
  • firstChild
  • lastChild
  • previousSibling
  • nextSibling

Finding elements

The complicating factor is that text nodes are created even for the whitespace between nodes. The example document’s body tag does not have just three children (h1 and two p elements) but actually has seven: those three, plus the spaces before, after, and between them.

Changing the document

  • removeChild
  • appendChild
  • insertBefore
  • replaceChild
1
2
3
4
5
6
7
8
<p>One</p>
<p>Two</p>
<p>Three</p>
<script>
var paragraphs = document.body.getElementsByTagName("p");
document.body.insertBefore(paragraphs[2], paragraphs[0]);
</script>

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).

Creating nodes

  • createElement
  • createTextNode
  • appendChild
  • replaceChild
  • insertChild
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>

The loop that goes over the images starts at the end of the list of nodes. This is necessary because the node list returned by a method like getElementsByTagName (or a property like childNodes) is live. That is, it is updated as the document changes. If we started from the front, removing the first image would cause the list to lose its first element so that the second time the loop repeats, where i is 1, it would stop because the length of the collection is now also 1.

If you want a solid collection of nodes, as opposed to a live one, you can convert the collection to a real array by calling the array slice method on it.

1
2
3
4
5
var arrayish = {0: "one", 1: "two", length: 2};
var real = Array.prototype.slice.call(arrayish, 0);
real.forEach(function(elt) { console.log(elt); });
// → one
// two

or just use querySelector

Attributes

Some element attributes, such as href for links, can be accessed through a property of the same name on the element’s DOM object. This is the case for a limited set of commonly used standard attributes.

But 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.

1
2
3
4
5
6
7
8
9
10
<p data-classified="secret">The launch code is 00000000.</p>
<p data-classified="unclassified">I have two feet.</p>
<script>
var paras = document.body.getElementsByTagName("p");
Array.prototype.forEach.call(paras, function(para) {
if (para.getAttribute("data-classified") == "secret")
para.parentNode.removeChild(para);
});
</script>

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

There is one commonly used attribute, class, which is a reserved word in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords or reserved words—the property used to access this attribute is called className. You can also access it under its real name, “class”, by using the getAttribute and setAttribute methods.

Layout

For any given document, browsers are able to compute a layout, which gives each element a size and position based on its type and content. This layout is then used to actually draw the document.

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.

1
2
3
4
var a = $('p');
a.getBoundingClientRect()
// DOMRect {x: 114, y: -12298.90625, width: 730, height: 116, top: -12298.90625, …
a.getBoundingClientRect().x; // 114

Styling

“color: red; border: none”

1
2
3
This text is displayed <strong>inline</strong>,
<strong style="display: block">as a block</strong>, and
<strong style="display: none">not at all</strong>.

JavaScript code can directly manipulate the style of an element through the node’s style property. This property holds an object that has properties for all possible style properties.

1
2
3
4
5
6
7
8
9
<p id="para" style="color: purple">
Pretty text
</p>
<script>
var para = document.getElementById("para");
console.log(para.style.color);
para.style.color = "magenta";
</script>

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.

Unlike methods such as getElementsByTagName, the object returned by querySelectorAll is not live. It won’t change when you change the document.

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.

Summary

JavaScript programs may inspect and interfere with the current document that a browser is displaying through a data structure called the DOM. This data structure represents the browser’s model of the document, and a JavaScript program can modify it to change the visible document.

The DOM is organized like a tree, in which elements are arranged hierarchically according to the structure of the document. The objects representing elements have properties such as parentNode and childNodes, which can be used to navigate through this tree.

The way a document is displayed can be influenced by styling, both by attaching styles to nodes directly and by defining rules that match certain nodes. There are many different style properties, such as color or display. JavaScript can manipulate an element’s style directly through its style property.

Exercises

Elements by tag name

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
32
33
34
35
36
37
38
39
40
41
42
43
<!doctype html>
<script src="code/mountains.js"></script>
<script src="code/chapter/13_dom.js"></script>
<style>
/* Defines a cleaner look for tables */
table { border-collapse: collapse; }
td, th { border: 1px solid black; padding: 3px 8px; }
th { text-align: left; }
</style>
<body>
<script>
function buildTable(data) {
var table = document.createElement("table");
var fields = Object.keys(data[0]);
var headRow = document.createElement("tr");
fields.forEach(function(field) {
var headCell = document.createElement("th");
headCell.textContent = field;
headRow.appendChild(headCell);
});
table.appendChild(headRow);
data.forEach(function(object) {
var row = document.createElement("tr");
fields.forEach(function(field) {
var cell = document.createElement("td");
cell.textContent = object[field];
if (typeof object[field] == "number")
cell.style.textAlign = "right";
row.appendChild(cell);
});
table.appendChild(row);
});
return table;
}
document.body.appendChild(buildTable(MOUNTAINS));
</script>
</body>

Elements by tag name

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
32
33
34
35
36
37
<!doctype html>
<script src="code/mountains.js"></script>
<script src="code/chapter/13_dom.js"></script>
<h1>Heading with a <span>span</span> element.</h1>
<p>A paragraph with <span>one</span>, <span>two</span>
spans.</p>
<script>
function byTagName(node, tagName) {
var found = [];
tagName = tagName.toUpperCase();
function explore(node) {
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType == document.ELEMENT_NODE) {
if (child.nodeName == tagName) {
found.push(child);
}
explore(child);
}
}
}
explore(node);
return found;
}
console.log(byTagName(document.body, "h1").length);
// → 1
console.log(byTagName(document.body, "span").length);
// → 3
var para = document.querySelector("p");
console.log(byTagName(para, "span").length);
// → 2
</script>

The cat’s hat

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
32
33
<!doctype html>
<script src="code/mountains.js"></script>
<script src="code/chapter/13_dom.js"></script>
<body style="min-height: 200px">
<img src="img/cat.png" id="cat" style="position: absolute">
<img src="img/hat.png" id="hat" style="position: absolute">
<script>
var cat = document.querySelector("#cat");
var hat = document.querySelector("#hat");
var angle = 0, lastTime = null;
function animate(time) {
if (lastTime != null) {
angle += (time - lastTime) * 0.0015;
}
lastTime = time;
cat.style.top = (Math.sin(angle) * 50 + 80) + "px";
cat.style.left = (Math.cos(angle) * 200 + 230) + "px";
// By adding π to the angle, the hat ends up half a circle ahead of the cat
var hatAngle = angle + Math.PI;
hat.style.top = (Math.sin(hatAngle) * 50 + 80) + "px";
hat.style.left = (Math.cos(hatAngle) * 200 + 230) + "px";
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
</script>
</body>

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

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 removeEventListener.

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
13
<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. 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.

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.

The following example registers “mousedown” handlers on both a button and the paragraph around it. When clicked with the right mouse button, the handler for the button calls stopPropagation, which will prevent the handler on the paragraph from running. When the button is clicked with another mouse button, both handlers will run.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<p>A paragraph with a <button>button</button>.</p>
<script>
var para = document.querySelector("p");
var button = document.querySelector("button");
para.addEventListener("mousedown", function() {
console.log("Handler for paragraph.");
});
button.addEventListener("mousedown", function(event) {
console.log("Handler for button.");
if (event.which == 3) {
event.stopPropagation();
}
});
</script>

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.

It is also possible to use the target property to cast a wide net for a specific type of event. For example, if you have a node containing a long list of buttons, it may be more convenient to register a single click handler on the outer node and have it use the target property to figure out whether a button was clicked, rather than register individual handlers on all of the buttons.

It called Event-Delegation, attention that the nodeName’s value is In the form of capital;

1
2
3
4
5
6
7
8
9
10
<button>A</button>
<button>B</button>
<button>C</button>
<script>
document.body.addEventListener("click", function(event) {
if (event.target.nodeName === "BUTTON") {
console.log("Clicked", event.target.textContent);
}
});
</script>

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>

Try not to do such things unless you have a really good reason to. For people using your page, it can be unpleasant when the behavior they expect is broken.

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.

Load event

When a page is closed or navigated away from (for example by following a link), a “beforeunload“ event fires.

The main use of this event is to prevent the user from accidentally losing work by closing a document.

Script execution timeline

There are various things that can cause a script to start executing. Reading a script tag is one such thing. An event firing is another. Chapter 13 discussed the requestAnimationFrame function, which schedules a function to be called before the next page redraw. That is yet another way in which a script can start running.

It is important to understand that even though events can fire at any time, no two scripts in a single document ever run at the same moment. If a script is already running, event handlers and pieces of code scheduled in other ways have to wait for their turn. This is the reason why a document will freeze when a script runs for a long time.

For cases where you really do want to do some time-consuming thing in the background without freezing the page, browsers provide something called web workers. A worker is an isolated JavaScript environment that runs alongside the main program for a document and can communicate with it only by sending and receiving messages.

Its global scope—which is a new global scope, not shared with the original script.

Setting timers

1
2
3
4
5
6
7
8
var bombTimer = setTimeout(function() {
console.log("BOOM!");
}, 500);
if (Math.random() < 0.5) { // 50% chance
console.log("Defused.");
clearTimeout(bombTimer);
}

A similar set of functions, setInterval and clearInterval are used to set timers that should repeat every X milliseconds.

1
2
3
4
5
6
7
8
var ticks = 0;
var clock = setInterval(function() {
console.log("tick", ticks++);
if (ticks == 10) {
clearInterval(clock);
console.log("stop.");
}
}, 200);

Debouncing

Some types of events have the potential to fire rapidly, many times in a row (the “mousemove” and “scroll” events, for example). When handling such events, you must be careful not to do anything too time-consuming or your handler will take up so much time that interaction with the document starts to feel slow and choppy.

1
2
3
4
5
6
7
8
9
10
11
<textarea>Type something here...</textarea>
<script>
var textarea = document.querySelector("textarea");
var timeout;
textarea.addEventListener("keydown", function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
console.log("You stopped typing.");
}, 500);
});
</script>

Giving an undefined value to clearTimeout or calling it on a timeout that has already fired has no effect. Thus, we don’t have to be careful about when to call it, and we simply do so for every event.

We can use a slightly different pattern if we want to space responses so that they’re separated by at least a certain length of time but want to fire them during a series of events, not just afterward. For example, we might want to respond to “mousemove” events by showing the current coordinates of the mouse, but only every 250 milliseconds.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
function displayCoords(event) {
document.body.textContent =
"Mouse at " + event.pageX + ", " + event.pageY;
}
var scheduled = false, lastEvent;
addEventListener("mousemove", function(event) {
lastEvent = event;
if (!scheduled) {
scheduled = true;
setTimeout(function() {
scheduled = false;
displayCoords(lastEvent);
}, 250);
}
});
</script>

Summary

Event handlers make it possible to detect and react to events we have no direct control over. The addEventListener method is used to register such a handler.

Each event has a type (“keydown”, “focus”, and so on) that identifies it. Most events are called on a specific DOM element and then propagate to that element’s ancestors, allowing handlers associated with those elements to handle them.

When an event handler is called, it is passed an event object with additional information about the event. This object also has methods that allow us to stop further propagation (stopPropagation) and prevent the browser’s default handling of the event (preventDefault).

Pressing a key fires “keydown”, “keypress”, and “keyup” events. Pressing a mouse button fires “mousedown”, “mouseup”, and “click” events. Moving the mouse fires “mousemove” and possibly “mouseenter” and “mouseout” events.

Scrolling can be detected with the “scroll” event, and focus changes can be detected with the “focus” and “blur” events. When the document finishes loading, a “load” event fires on the window.

Only one piece of JavaScript program can run at a time (Web workers). Thus, event handlers and other scheduled scripts have to wait until other scripts finish before they get their turn.

Exercises

Censored keyboard

1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<input type="text">
<script>
var field = document.querySelector("input");
field.addEventListener("keydown", function(event) {
if (event.keyCode === "Q".charCodeAt(0) ||
event.keyCode === "W".charCodeAt(0) ||
event.keyCode === "X".charCodeAt(0)) {
event.preventDefault();
}
});
</script>

Mouse trail

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
32
33
34
35
36
37
<!doctype html>
<style>
.trail { /* className for the trail elements */
position: absolute;
height: 6px; width: 6px;
border-radius: 3px;
background: teal;
}
body {
height: 300px;
}
</style>
<body>
<script>
var dots = [];
var i;
var node = document.createElement("div");
for (i = 0; i < 12; i++) {
node.className = "trail";
dots.push(node);
}
document.body.appendChild(dots);
addEventListener("mousemove", function(event) {
var currentDot = 0;
var dot = dots[currentDot];
dot.style.left = (event.pageX - 3) + "px";
dot.style.top = (event.pageY - 3) + "px";
currentDot = (currentDot + 1) % dots.length;
});
</script>
</body>

Tabs

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!doctype html>
<div id="wrapper">
<div data-tabname="one">Tab one</div>
<div data-tabname="two">Tab two</div>
<div data-tabname="three">Tab three</div>
</div>
<script>
function asTabs(node) {
var tabs = [];
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType === 1) {
tabs.push(child);
}
}
var tabList = document.createElement("div");
tabs.forEach(function(tab, i) {
var button = document.createElement("button");
button.textContent = tab.getAttribute("data-tabname");
button.addEventListener("click", function() {
selectTab(i);
});
tabList.appendChild(button);
});
node.insertBefore(tabList, node.firstChild);
function selectTab(n) {
tabs.forEach(function(tab, i) {
if (i === n) {
tab.style.display = "";
} else {
tab.style.display = "none";
}
});
for (var i = 0; i < tabList.childNodes.length; i++) {
if (i === n) {
tabList.childNodes[i].style.background = "violet";
} else {
tabList.childNodes[i].style.background = "";
}
}
}
selectTab(0);
}
asTabs(document.querySelector("#wrapper"));
</script>

Drawing on Canvas

Browsers give us several ways to display graphics. The simplest way is to use styles to position and color regular DOM elements. This can get you quite far, as the game in the previous chapter showed. By adding partially transparent background images to the nodes, we can make them look exactly the way we want. It is even possible to rotate or skew nodes by using the transform style.

But we’d be using the DOM for something that it wasn’t originally designed for. Some tasks, such as drawing a line between arbitrary points, are extremely awkward to do with regular HTML elements.

There are two alternatives. The first is DOM-based but utilizes Scalable Vector Graphics (SVG), rather than HTML elements. Think of SVG as a dialect for describing documents that focuses on shapes rather than text. You can embed an SVG document in an HTML document, or you can include it through an img tag.

The second alternative is called a canvas. A canvas is a single DOM element that encapsulates a picture. It provides a programming interface for drawing shapes onto the space taken up by the node.

The main difference between a canvas and an SVG picture is that

  • in SVG the original description of the shapes is preserved so that they can be moved or resized at any time.
  • A canvas, on the other hand, converts the shapes to pixels (colored dots on a raster) as soon as they are drawn and does not remember what these pixels represent. The only way to move a shape on a canvas is to clear the canvas (or the part of the canvas around the shape) and redraw it with the shape in a new position.

SVG

This book will not go into SVG in detail, but I will briefly explain how it works. At the end of the chapter, I’ll come back to the trade-offs that you must consider when deciding which drawing mechanism is appropriate for a given application.

This is an HTML document with a simple SVG picture in it:

1
2
3
4
5
6
<p>Normal HTML here.</p>
<svg xmlns="http://www.w3.org/2000/svg">
<circle r="50" cx="50" cy="50" fill="red"/>
<rect x="120" y="5" width="90" height="90"
stroke="blue" fill="none"/>
</svg>

The xmlns attribute changes an element (and its children) to a different XML namespace. This namespace, identified by a URL, specifies the dialect that we are currently speaking. Theandtags, which do not exist in HTML, do have a meaning in SVG—they draw shapes using the style and position specified by their attributes.

These tags create DOM elements, just like HTML tags. For example, this changes theelement to be colored cyan instead:

1
2
var circle = document.querySelector("circle");
circle.setAttribute("fill", "cyan");

The canvas element

Canvas graphics can be drawn onto aelement. You can give such an element width and height attributes to determine its size in pixels.

A new canvas is empty, meaning it is entirely transparent and thus shows up simply as empty space in the document.

The canvas tag is intended to support different styles of drawing. To get access to an actual drawing interface, we first need to create a context, which is an object whose methods provide the drawing interface.

There are currently two widely supported drawing styles: “2d” for two-dimensional graphics and “WebGL” for three-dimensional graphics through the OpenGL interface.

This book won’t discuss WebGL. We stick to two dimensions. But if you are interested in three-dimensional graphics, I do encourage you to look into WebGL. It provides a very direct interface to modern graphics hardware and thus allows you to render even complicated scenes efficiently, using JavaScript.

A context is created through the getContext method on the canvas element.

1
2
3
4
5
6
7
8
9
<p>Before canvas.</p>
<canvas width="120" height="60"></canvas>
<p>After canvas.</p>
<script>
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
context.fillStyle = 'cyan';
context.fillRect(20, 20, 200, 200);
</script>

After creating the context object, the example draws a red rectangle 100 pixels wide and 50 pixels high, with its top-left corner at coordinates (10,10).

Just like in HTML (and SVG), the coordinate system that the canvas uses puts (0,0) at the top-left corner, and the positive y-axis goes down from there.

Filling and stroking

A similar method, strokeRect, draws the outline of a rectangle.

Neither method takes any further parameters. The color of the fill, thickness of the stroke, and so on are not determined by an argument to the method (as you might justly expect) but rather by properties of the context object.

1
2
3
4
5
6
7
8
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.strokeStyle = "blue";
cx.strokeRect(5, 5, 50, 50);
cx.lineWidth = 5;
cx.strokeRect(135, 5, 50, 50);
</script>

When no width or height attribute is specified, as in the previous example, a canvas element gets a default width of 300 pixels and height of 150 pixels.

Paths

A path is a sequence of lines. The 2D canvas interface takes a peculiar approach to describing such a path. It is done entirely through side effects. Paths are not values that can be stored and passed around. Instead, if you want to do something with a path, you make a sequence of method calls to describe its shape. (stroke method)

1
2
3
4
5
6
7
8
9
10
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
for (var y = 10; y < 100; y += 10) {
cx.moveTo(10, y);
cx.lineTo(90, y);
}
cx.stroke();
</script>

When filling a path (using the fill method), each shape is filled separately. A path can contain multiple shapes—each moveTo motion starts a new one. But the path needs to be closed (meaning its start and end are in the same position) before it can be filled. If the path is not already closed, a line is added from its end to its start, and the shape enclosed by the completed path is filled. (fill method)

1
2
3
4
5
6
7
8
9
10
<canvas></canvas>
<script>
var cx = document.querySelector('canvas').getContext('2d');
cx.beginPath();
cx.moveTo(50, 10);
cx.lineTo(10, 70);
cx.lineTo(90, 70);
cx.closePath();
cx.fill();
</script>

Curves

quadraticCurveTo

1
2
3
4
5
6
7
8
9
10
11
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
cx.moveTo(10, 90);
// control=(60,10) goal=(90,90)
cx.quadraticCurveTo(60, 10, 90, 90);
cx.lineTo(60, 10);
cx.closePath();
cx.stroke();
</script>

bezierCurveTo

The bezierCurveTo method draws a similar kind of curve. Instead of a single control point, this one has two—one for each of the line's endpoints. Here is a similar sketch to illustrate the behavior of such a curve:

1
2
3
4
5
6
7
8
9
10
11
12
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
cx.moveTo(10, 90);
// control1=(10,10) control2=(90,10) goal=(50,90)
cx.bezierCurveTo(10, 10, 90, 10, 50, 90);
cx.lineTo(90, 10);
cx.lineTo(10, 10);
cx.closePath();
cx.stroke();
</script>

Such curves can be hard to work with—it’s not always clear how to find the control points that provide the shape you are looking for. Sometimes you can compute them, and sometimes you’ll just have to find a suitable value by trial and error.

Arcs—fragments of a circle—are easier to reason about.

1
2
3
4
5
6
7
8
9
10
11
12
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
cx.moveTo(10, 10);
// control=(90,10) goal=(90,90) radius=20
cx.arcTo(90, 10, 90, 90, 20);
cx.moveTo(10, 10);
// control=(90,10) goal=(90,90) radius=80
cx.arcTo(90, 10, 90, 90, 80);
cx.stroke();
</script>

To draw a circle, you could use four calls to arcTo (each turning 90 degrees). But the arc method provides a simpler way. It takes a pair of coordinates for the arc’s center, a radius, and then a start and end angle.

1
2
3
4
5
6
7
8
9
10
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.beginPath();
// center=(50,50) radius=40 angle=0 to 7
cx.arc(50, 50, 40, 0, 7);
// center=(150,50) radius=40 angle=0 to ½π
cx.arc(150, 50, 40, 0, 0.5 * Math.PI);
cx.stroke();
</script>

Drawing a pie chart

To draw a pie chart, we draw a number of pie slices, each made up of an arc and a pair of lines to the center of that arc. We can compute the angle taken up by each arc by dividing a full circle (2π) by the total number of responses and then multiplying that number (the angle per response) by the number of people who picked a given choice.

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
<canvas width="200" height="200"></canvas>
<script>
var results = [
{name: "Satisfied", count: 1043, color: "lightblue"},
{name: "Neutral", count: 563, color: "lightgreen"},
{name: "Unsatisfied", count: 510, color: "pink"},
{name: "No comment", count: 175, color: "silver"}
];
var cx = document.querySelector("canvas").getContext("2d");
var total = results.reduce(function(sum, choice) {
return sum + choice.count;
}, 0);
// Start at the top
var currentAngle = -0.5 * Math.PI;
results.forEach(function(result) {
var sliceAngle = (result.count / total) * 2 * Math.PI;
cx.beginPath();
// center=100,100, radius=100
// from current angle, clockwise by slice's angle
cx.arc(100, 100, 100,
currentAngle, currentAngle + sliceAngle);
currentAngle += sliceAngle;
cx.lineTo(100, 100);
cx.fillStyle = result.color;
cx.fill();
});
</script>

But a chart that doesn’t tell us what it means isn’t very helpful. We need a way to draw text to the canvas.

Text

A 2D canvas drawing context provides the methods fillText and strokeText. The latter can be useful for outlining letters, but usually fillText is what you need. It will fill the given text with the current fillColor.

1
2
3
4
5
6
7
<canvas></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
cx.font = "28px Georgia";
cx.fillStyle = "fuchsia";
cx.fillText("I can draw text, too!", 10, 50);
</script>

Images

In computer graphics, a distinction is often made between vector graphics and bitmap graphics. The first is what we have been doing so far in this chapter—specifying a picture by giving a logical description of shapes. Bitmap graphics, on the other hand, don’t specify actual shapes but rather work with pixel data (rasters of colored dots).

Images

Transformation

Summary

HTTP

The Hypertext Transfer Protocol(HTTP) is the mechanism through which data is requested and provided on the World Wide Web. This chapter describes the protocol in more detail and explains the way browser JavaScript has access to it.

The protocol

  • Status codes starting with a 2 indicate that the request succeeded.
  • Codes starting with 4 mean there was something wrong with the request. 404 is probably the most famous HTTP status code—it means that the resource that was requested could not be found.
  • Codes that start with 5 mean an error happened on the server and the request is not to blame.

Browsers and HTTP

A moderately complicated website can easily include anywhere from 10 to 200 resources. To be able to fetch those quickly, browsers will make several requests simultaneously, rather than waiting for the responses one at a time. Such documents are always fetched using GET requests.

HTML pages may include forms, which allow the user to fill out information and send it to the server. This is an example of a form:

1
2
3
4
5
<form method="GET" action="example/message.html">
<p>Name: <input type="text" name="name"></p>
<p>Message:<br><textarea name="message"></textarea></p>
<p><button type="submit">Send</button></p>
</form>

When the form element’s method attribute is GET (or is omitted), that query string is tacked onto the action URL, and the browser makes a GET request to that URL.

1
GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1

URL encoding

JavaScript provides the encodeURIComponent and decodeURIComponent functions to encode and decode this format.

1
2
3
4
console.log(encodeURIComponent("Hello & goodbye"));
// → Hello%20%26%20goodbye
console.log(decodeURIComponent("Hello%20%26%20goodbye"));
// → Hello & goodbye

POST

By convention, the GET method is used for requests that do not have side effects, such as doing a search. Requests that change something on the server, such as creating a new account or posting a message, should be expressed with other methods, such as POST.

Client-side software, such as a browser, knows that it shouldn’t blindly make POST requests but will often implicitly make GET requests—for example, to prefetch a resource it believes the user will soon need.

XMLHttpRequest

The interface through which browser JavaScript can make HTTP requests is called XMLHttpRequest.

When the XMLHttpRequest interface was added to Internet Explorer, it allowed people to do things with JavaScript that had been very hard before. For example, websites started showing lists of suggestions when the user was typing something into a text field. The script would send the text to the server over HTTP as the user typed. The server, which had some database of possible inputs, would match the database entries against the partial input and send back possible completions to show the user. This was considered spectacular—people were used to waiting for a full page reload for every interaction with a website.

Sending a request

On Mac:

1
open /Applications/Chromium.app --args --disable-web-security

To make a simple request, we create a request object with the XMLHttpRequest constructor and call its open and send methods.

1
2
3
4
5
var req = new XMLHttpRequest();
req.open("GET", "http://rayjune.xyz", false);
req.send(null);
console.log(req.responseText);
// → This is the content of data.txt

The open method configures the request. In this case, we choose to make a GET request for the example/data.txt file. URLs that don’t start with a protocol name (such as http:) are relative, which means that they are interpreted relative to the current document.

After opening the request, we can send it with the send method. The argument to send is the request body. For GET requests, we can pass null.

If the third argument to open was false,send will return only after the response to our request was received. We can read the request object’s responseText property to get the response body.

The other information included in the response can also be extracted from this object. The status code is accessible through the status property, and the human-readable status text is accessible through statusText. Headers can be read with getResponseHeader.

1
2
3
4
5
6
7
var req = new XMLHttpRequest();
req.open("GET", "http://rayjune.xyz", false);
req.send(null);
console.log(req.status, req.statusText);
// → 200 OK
console.log(req.getResponseHeader("content-type"));
// → text/plain

Asynchronous Requests

If we pass true as the third argument to open, the request is asynchronous. This means that when we call send, the only thing that happens right away is that the request is scheduled to be sent. Our program can continue, and the browser will take care of the sending and receiving of data in the background.

1
2
3
4
5
6
var req = new XMLHttpRequest();
req.open("GET", "http://rayjune.xyz", true);
req.addEventListener("load", function() {
console.log("Done:", req.status);
});
req.send(null);

Fetching XML Data

1
2
3
4
5
<fruits>
<fruit name="banana" color="yellow"/>
<fruit name="lemon" color="yellow"/>
<fruit name="cherry" color="red"/>
</fruits>
1
2
3
4
5
var req = new XMLHttpRequest();
req.open("GET", "example/fruit.xml", false);
req.send(null);
console.log(req.responseXML.querySelectorAll("fruit").length);
// → 3

XML documents can be used to exchange structured information with the server. Their form—tags nested inside other tags—lends itself well to storing most types of data, or at least better than flat text files.

The DOM interface is rather clumsy for extracting information, though, and XML documents tend to be verbose. It is often a better idea to communicate using JSON data, which is easier to read and write, both for programs and for humans.

1
2
3
4
5
var req = new XMLHttpRequest();
req.open("GET", "http://rayjune.xyz/JustToDo/package.json", false);
req.send(null);
console.log(JSON.parse(req.responseText));
// Object {name: "just-to-do", version: "1.0.0", description: "就是去做", main: ".eslintrc.js", scripts: Object…}

HTTP sandboxing

Making HTTP requests in web page scripts once again raises concerns about security. The person who controls the script might not have the same interests as the person on whose computer it is running. More specifically, if I visit themafia.org, I do not want its scripts to be able to make a request to mybank.com, using identifying information from my browser, with instructions to transfer all my money to some random mafia account.

It is possible for websites to protect themselves against such attacks, but that requires effort, and many websites fail to do it. For this reason, browsers protect us by disallowing scripts to make HTTP requests to other domains (names such as themafia.org and mybank.com).

This can be an annoying problem when building systems that want to access several domains for legitimate reasons. Fortunately, servers can include a header like this in their response to explicitly indicate to browsers that it is okay for the request to come from other domains:

Access-Control-Allow-Origin: *

Abstracting requests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function backgroundReadFile(url, callback) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400) {
callback(req.responseText);
}
});
req.send(null);
}
// invoke it
backgroundReadFile('http://rayjune.xyz', function (req) {
console.log(req);
});

This simple abstraction makes it easier to use XMLHttpRequest for simple GET requests. If you are writing a program that has to make HTTP requests, it is a good idea to use a helper function so that you don’t end up repeating the ugly XMLHttpRequest pattern all through your code.

The previous one does only GET requests and doesn’t give us control over the headers or the request body. You could write another variant for POST requests or a more generic one that supports various kinds of requests. Many JavaScript libraries also provide wrappers for XMLHttpRequest.

The main problem with the previous wrapper is its handling of failure. When the request returns a status code that indicates an error (400 and up), it does nothing. This might be okay, in some circumstances, but imagine we put a “loading” indicator on the page to indicate that we are fetching information. If the request fails because the server crashed or the connection is briefly interrupted, the page will just sit there, misleadingly looking like it is doing something. The user will wait for a while, get impatient, and consider the site uselessly flaky.

We should also have an option to be notified when the request fails so that we can take appropriate action. For example, we could remove the “loading” message and inform the user that something went wrong.

Error handling in asynchronous code is even trickier than error handling in synchronous code. Because we often need to defer part of our work, putting it in a callback function, the scope of a try block becomes meaningless. In the following code, the exception will not be caught because the call to backgroundReadFile returns immediately. Control then leaves the try block, and the function it was given won’t be called until later.

1
2
3
4
5
6
7
8
9
try {
backgroundReadFile("http://rayjune.xyz", function(text) {
if (text !== "expected") {
throw new Error("That was unexpected");
}
});
} catch (e) {
console.log("Hello from the catch block");
}

To handle failing requests, we have to allow an additional function to be passed to our wrapper and call that when a request goes wrong. Alternatively, we can use the convention that if the request fails, an additional argument describing the problem is passed to the regular callback function. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getURL(url, callback) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400)
callback(req.responseText);
else
callback(null, new Error("Request failed: " +
req.statusText));
});
req.addEventListener("error", function() {
callback(null, new Error("Network error"));
});
req.send(null);
}

We have added a handler for the "error" event, which will be signaled when the request fails entirely.
We also call the callback function with an error argument when the request completes with a status code that indicates an error.

Code using getURL must then check whether an error was given and, if it finds one, handle it.

1
2
3
4
5
6
7
getURL("data/nonsense.txt", function(content, error) {
if (error != null) {
console.log("Failed to fetch nonsense.txt: " + error);
} else {
console.log("nonsense.txt: " + content);
}
});

Promises

For complicated projects, writing asynchronous code in plain callback style is hard to do correctly. It is easy to forget to check for an error or to allow an unexpected exception to cut the program short in a crude way. Additionally, arranging for correct error handling when the error has to flow through multiple callback functions and catch blocks is tedious.

One of the more successful solve abstractions ones is called promises. Promises wrap an asynchronous action in an object, which can be passed around and told to do certain things when the action finishes or fails. This interface is set to become part of the next version of the JavaScript language but can already be used as a library.

The interface for promises isn’t entirely intuitive, but it is powerful.

To create a promise object, we call the Promise constructor, giving it a function that initializes the asynchronous action. The constructor calls that function, passing it two arguments, which are themselves functions. The first should be called when the action finishes successfully, and the second should be called when it fails.

Once again, here is our wrapper for GET requests, this time returning a promise. We’ll simply call it get this time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function get(url) {
return new Promise(function(succeed, fail) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400) {
succeed(req.responseText);
} else {
fail(new Error("Request failed: " + req.statusText));
}
});
req.addEventListener("error", function() {
fail(new Error("Network error"));
});
req.send(null);
});
}

Note that the interface to the function itself is now a lot simpler.
You give it a URL, and it returns a promise.

That promise acts as a handle to the request’s outcome. It has a then method that you can call with two functions: one to handle success and one to handle failure.

1
2
3
4
5
get("example/data.txt").then(function(text) {
console.log("data.txt: " + text);
}, function(error) {
console.log("Failed to fetch data.txt: " + error);
});

You can think of the promise interface as implementing its own language for asynchronous control flow.

The extra method calls and function expressions needed to achieve this make the code look somewhat awkward but not remotely as awkward as it would look if we took care of all the error handling ourselves.

Calling then produces a new promise, whose result (the value passed to success handlers) depends on the return value of the first function we passed to then. This function may return another promise to indicate that more asynchronous work is being done.

1
2
3
function getJSON(url) {
return get(url).then(JSON.parse);
}

That last call to then did not specify a failure handler. This is allowed. The error will be passed to the promise returned by then, which is exactly what we want—getJSON does not know what to do when something goes wrong, but hopefully its caller does.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
function showMessage(msg) {
var elt = document.createElement("div");
elt.textContent = msg;
return document.body.appendChild(elt);
}
var loading = showMessage("Loading...");
getJSON("example/bert.json").then(function(bert) {
return getJSON(bert.spouse);
}).then(function(spouse) {
return getJSON(spouse.mother);
}).then(function(mother) {
showMessage("The name is " + mother.name);
}).catch(function(error) {
showMessage(String(error));
}).then(function() {
document.body.removeChild(loading);
});
</script>

Security and HTTPS

The secure HTTP protocol, whose URLs start with https://, wraps HTTP traffic in a way that makes it harder to read and tamper with. First, the client verifies that the server is who it claims to be by requiring that server to prove that it has a cryptographic certificate issued by a certificate authority that the browser recognizes. Next, all data going over the connection is encrypted in a way that should prevent eavesdropping and tampering.

It is not perfect, and there have been various incidents where HTTPS failed because of forged or stolen certificates and broken software. Still, plain HTTP is trivial to mess with, whereas breaking HTTPS requires the kind of effort that only states or sophisticated criminal organizations can hope to make.

Summary

In this chapter, we saw that HTTP is a protocol for accessing resources over the Internet. A client sends a request, which contains a method (usually GET) and a path that identifies a resource. The server then decides what to do with the request and responds with a status code and a response body. Both requests and responses may contain headers that provide additional information.

Browsers make GET requests to fetch the resources needed to display a web page. A web page may also contain forms, which allow information entered by the user to be sent along in the request made when the form is submitted. You will learn more about that in the next chapter.

The interface through which browser JavaScript can make HTTP requests is called XMLHttpRequest. You can usually ignore the “XML” part of that name (but you still have to type it). There are two ways in which it can be used—synchronous, which blocks everything until the request finishes, and asynchronous, which requires an event handler to notice that the response came in. In almost all cases, asynchronous is preferable. Making a request looks like this:

1
2
3
4
5
6
var req = new XMLHttpRequest();
req.open("GET", "example/data.txt", true);
req.addEventListener("load", function() {
console.log(req.status);
});
req.send(null);

Asynchronous programming is tricky. Promises are an interface that makes it slightly easier by helping route error conditions and exceptions to the right handler and by abstracting away some of the more repetitive and error-prone elements in this style of programming.

Exercises

Content negotiation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function requestAuthor(type) {
var req = new XMLHttpRequest();
req.open("GET", "http://eloquentjavascript.net/author", false);
req.setRequestHeader("accept", type);
req.send(null);
return req.responseText;
}
var types = ["text/plain",
"text/html",
"application/json",
"application/rainbows+unicorns"];
types.forEach(function(type) {
try {
console.log(type + ":\n", requestAuthor(type), "\n");
} catch (e) {
console.log("Raised error: " + e);
}
});

Waiting for multiple promises

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
32
33
34
35
36
37
38
39
40
41
42
43
44
function all(promises) {
return new Promise(function(succeed, fail) {
var results = [], pending = promises.length;
promises.forEach(function(promise, i) {
promise.then(function(result) {
results[i] = result;
pending -= 1;
if (pending == 0) {
succeed(results);
}
}, function(error) {
fail(error);
});
});
if (promises.length == 0) {
succeed(results);
}
});
}
// Test code.
all([]).then(function(array) {
console.log("This should be []:", array);
});
function soon(val) {
return new Promise(function(success) {
setTimeout(function() { success(val); },
Math.random() * 500);
});
}
all([soon(1), soon(2), soon(3)]).then(function(array) {
console.log("This should be [1, 2, 3]:", array);
});
function fail() {
return new Promise(function(success, fail) {
fail(new Error("boom"));
});
}
all([soon(1), fail(), soon(3)]).then(function(array) {
console.log("We should not get here");
}, function(error) {
if (error.message != "boom")
console.log("Unexpected failure:", error);
});

Forms and Form Fields

Fields

A web form consists of any number of input fields grouped in a form tag.
A lot of field types use the input tag.

Form fields do not necessarily have to appear in a

tag. You can put them anywhere in a page. Such fields cannot be submitted (only a form as a whole can), but when responding to input with JavaScript, we often do not want to submit our fields normally anyway.

1
2
3
4
5
6
7
<p><input type="text" value="abc"> (text)</p>
<p><input type="password" value="abc"> (password)</p>
<p><input type="checkbox" checked> (checkbox)</p>
<p><input type="radio" value="A" name="choice">
<input type="radio" value="B" name="choice" checked>
<input type="radio" value="C" name="choice"> (radio)</p>
<p><input type="file"> (file)</p>

and textarea tag

1
2
3
4
5
<textarea>
one
two
three
</textarea>

Whenever the value of a form field changes, select fires a “change” event.

1
2
3
4
5
<select>
<option>Pancakes</option>
<option>Pudding</option>
<option>Ice cream</option>
</select>

Focus

Unlike most elements in an HTML document, form fields can get keyboard focus. When clicked—or activated in some other way—they become the currently active element, the main recipient of keyboard input.

We can control focus from JavaScript with the focus and blur methods. The first moves focus to the DOM element it is called on, and the second removes focus. The value in document.activeElement corresponds to the currently focused element.

1
2
3
4
5
6
7
8
9
10
11
12
13
<input type="text">
<script>
document.querySelector("input").focus();
console.log(document.activeElement.tagName);
// → INPUT
setTimeout(testBlur, 2000);
function testBlur() {
document.querySelector("input").blur();
console.log(document.activeElement.tagName);
// → BODY
}
</script>

For some pages, the user is expected to want to interact with a form field immediately. JavaScript can be used to focus this field when the document is loaded, but HTML also provides the autofocus attribute, which produces the same effect but lets the browser know what we are trying to achieve. This makes it possible for the browser to disable the behavior when it is not appropriate, such as when the user has focused something else.

1
<input type="text" autofocus>

Browsers traditionally also allow the user to move the focus through the document by pressing the Tab key. We can influence the order in which elements receive focus with the tabindex attribute. The following example document will let focus jump from the text input to the OK button, rather than going through the help link first:

1
2
<input type="text" tabindex=1> <a href=".">(help)</a>
<button onclick="console.log('ok')" tabindex=2>OK</button>

By default, most types of HTML elements cannot be focused. But you can add a tabindex attribute to any element, which will make it focusable.

Disabled fields

All form fields can be disabled through their disabled attribute, which also exists as a property on the element’s DOM object.

1
2
<button>I'm all right</button>
<button disabled>I'm out</button>

Disabled fields cannot be focused or changed, and unlike active fields, they usually look gray and faded.

When a program is in the process of handling an action caused by some button or other control, which might require communication with the server and thus take a while, it can be a good idea to disable the control until the action finishes. That way, when the user gets impatient and clicks it again, they don’t accidentally repeat their action.

The form as a whole

When a field is contained in a

element, its DOM element will have a property form linking back to the form’s DOM element. Theelement, in turn, has a property called elements that contains an array-like collection of the fields inside it.

1
2
3
4
5
6
7
8
9
10
11
12
13
<form action="example/submit.html">
Name: <input type="text" name="name"><br>
Password: <input type="password" name="password"><br>
<button type="submit">Log in</button>
</form>
<script>
var form = document.querySelector('form');
console.log(form);
console.log(form.elements);
console.log(form.elements[1].type);
console.log(form.elements.password.type);
console.log(form.elements.name.form === form);
</script>

A button with a type attribute of submit will, when pressed, cause the form to be submitted. Pressing Enter when a form field is focused has the same effect.

Submitting a form normally means that the browser navigates to the page indicated by the form’s action attribute, using either a GET or a POST request. But before that happens, a "submit" event is fired. This event can be handled by JavaScript, and the handler can prevent the default behavior by calling preventDefault on the event object.

1
2
3
4
5
6
7
8
9
10
11
<form action="example/submit.html">
Value: <input type="text" name="value">
<button type="submit">Save</button>
</form>
<script>
var form = document.querySelector("form");
form.addEventListener("submit", function(event) {
console.log("Saving value", form.elements.value.value);
event.preventDefault();
});
</script>

Intercepting “submit” events in JavaScript has various uses. We can write code to verify that the values the user entered make sense and immediately show an error message instead of submitting the form when they don’t.

Or we can disable the regular way of submitting the form entirely, as in the previous example, and have our program handle the input, possibly using XMLHttpRequest to send it over to a server without reloading the page.

Text fields

Fields created by input tags with a type of text or password, as well as textarea tags, share a common interface. Their DOM elements have a value property that holds their current content as a string value. Setting this property to another string changes the field’s content.

The selectionStart and selectionEnd properties of text fields give us information about the cursor and selection in the text. When nothing is selected, these two properties hold the same number, indicating the position of the cursor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<textarea></textarea>
<script>
var textarea = document.querySelector("textarea");
textarea.addEventListener("keydown", function(event) {
// The key code for left-shift happens to be 16
if (event.keyCode === 16) {
replaceSelection(textarea, "rayjune.xyz");
event.preventDefault();
}
});
function replaceSelection(field, word) {
var from = field.selectionStart;
var to = field.selectionEnd;
field.value = field.value.slice(0, from) + word +
field.value.slice(to);
// Put the cursor after the word
field.selectionStart = from + word.length;
field.selectionEnd = field.selectionStart;
}
</script>

The “change” event for a text field does not fire every time something is typed. Rather, it fires when the field loses focus after its content was changed.

To respond immediately to changes in a text field, you should register a handler for the “input” event instead, which fires for every time the user types a character, deletes text, or otherwise manipulates the field’s content.

1
2
3
4
5
6
7
8
<input type="text"> length: <span id="length">0</span>
<script>
var text = document.querySelector("input");
var output = document.querySelector("#length");
text.addEventListener("input", function() {
output.textContent = text.value.length;
});
</script>

Checkboxes and radio buttons

1
2
3
4
5
6
7
8
<input type="checkbox" id="teal">
<label for="teal">Make this page teal</label>
<script>
var checkbox = document.querySelector('#teal');
checkbox.addEventListener('change', function () {
document.body.style.background = checkbox.checked ? 'teal' : '';
});
</script>

another example

1
2
3
4
5
6
7
8
9
10
11
12
13
Color:
<input type="radio" name="color" value="mediumpurple"> Purple
<input type="radio" name="color" value="lightgreen"> Green
<input type="radio" name="color" value="lightblue"> Blue
<script>
var buttons = document.getElementsByName("color");
function setColor(e) {
document.body.style.background = e.target.value;
}
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('change', setColor);
}
</script>

The document.getElementsByName method gives us all elements with a given name attribute.

The example loops over those (with a regular for loop, not forEach, because the returned collection is not a real array)

Select fields

1
2
3
4
5
<select multiple>
<option>Pancakes</option>
<option>Pudding</option>
<option>Ice cream</option>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<select multiple>
<option value="1">0001</option>
<option value="2">0010</option>
<option value="4">0100</option>
<option value="8">1000</option>
</select> = <span id="output">0</span>
<script>
var select = document.querySelector("select");
var output = document.querySelector("#output");
select.addEventListener("change", function() {
var number = 0;
for (var i = 0; i < select.options.length; i++) {
var option = select.options[i];
if (option.selected) {
number += Number(option.value);
}
}
output.textContent = number;
});
</script>

File fields

1
2
3
4
5
6
7
8
9
10
11
12
13
<input type="file">
<script>
var input = document.querySelector("input");
input.addEventListener("change", function() {
if (input.files.length > 0) {
var file = input.files[0];
console.log("You chose", file.name);
if (file.type) {
console.log("It has type", file.type);
}
}
});
</script>

What it does not have is a property that contains the content of the file. Getting at that is a little more involved. Since reading a file from disk can take time, the interface will have to be asynchronous to avoid freezing the document. You can think of the FileReader constructor as being similar to XMLHttpRequest but for files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<input type="file" multiple>
<script>
var input = document.querySelector("input");
input.addEventListener("change", function() {
Array.prototype.forEach.call(input.files, function(file) {
var reader = new FileReader();
reader.addEventListener("load", function() {
console.log("File", file.name, "starts with",
reader.result.slice(0, 20));
});
reader.readAsText(file);
});
});
</script>

FileReaders also fire an “error” event when reading the file fails for any reason. The error object itself will end up in the reader’s error property. If you don’t want to remember the details of yet another inconsistent asynchronous interface, you could wrap it in a Promise like this:

1
2
3
4
5
6
7
8
9
10
11
12
function readFile(file) {
return new Promise(function(succeed, fail) {
var reader = new FileReader();
reader.addEventListener("load", function() {
succeed(reader.result);
});
reader.addEventListener("error", function() {
fail(reader.error);
});
reader.readAsText(file);
});
}

Storing data client-side

When such an application needs to remember something between sessions, you cannot use JavaScript variables since those are thrown away every time a page is closed. You could set up a server, connect it to the Internet, and have your application store something there.

But this adds a lot of extra work and complexity. Sometimes it is enough to just keep the data in the browser. But how?

You can store string data in a way that survives page reloads by putting it in the localStorage object. This object allows you to file string values under names (also strings), as in this example:

1
2
3
4
localStorage.setItem("username", "marijn");
console.log(localStorage.getItem("username"));
// → marijn
localStorage.removeItem("username");

There is another object similar to localStorage called sessionStorage. The difference between the two is that the content of sessionStorage is forgotten at the end of each session, which for most browsers means whenever the browser is closed.

Summary

HTML can express various types of form fields, such as text fields, checkboxes, multiple-choice fields, and file pickers.

Such fields can be inspected and manipulated with JavaScript. They fire the "change" event when changed, the "input" event when text is typed, and various keyboard events (keydown). These events allow us to notice when the user is interacting with the fields. Properties like value (for text and select fields) or checked (for checkboxes and radio buttons) are used to read or set the field’s content.

When a form is submitted, its "submit" event fires. A JavaScript handler can call preventDefault on that event to prevent the submission from happening. Form field elements do not have to be wrapped in form tags.

When the user has selected a file from their local file system in a file picker field, the FileReader interface can be used to access the content of this file from a JavaScript program.

The localStorage and sessionStorage objects can be used to save information in a way that survives page reloads. The first saves the data forever (or until the user decides to clear it), and the second saves it until the browser is closed.

Exercises

A JavaScript workbench

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<textarea id="code">return "hi";</textarea>
<button id="button">Run</button>
<pre id="output"></pre>
<script>
document.querySelector('#button').addEventListener('click', function () {
var code = document.querySelector('#code').value;
var outputNode = document.querySelector('#output');
try {
var result = new Function(code)();
outputNode.innerText = String(result);
} catch (e) {
outputNode.innerText = 'Error: ' + e;
}
});
</script>
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
32
<!doctype html>
<script src="code/promise.js"></script>
<input type="text" id="field">
<div id="suggestions" style="cursor: pointer"></div>
<script>
// Builds up an array with global variable names, like
// 'alert', 'document', and 'scrollTo'
var terms = [];
for (var name in window)
terms.push(name);
var textfield = document.querySelector("#field");
var suggestions = document.querySelector("#suggestions");
textfield.addEventListener("input", function() {
var matching = terms.filter(function(term) {
return term.indexOf(textfield.value) == 0;
});
suggestions.textContent = "";
matching.slice(0, 20).forEach(function(term) {
var node = document.createElement("div");
node.textContent = term;
node.addEventListener("click", function() {
textfield.value = term;
suggestions.textContent = "";
});
suggestions.appendChild(node);
});
});
</script>
1
2
3
4
5
6
7
8
9
<!doctype html>
<script src="code/promise.js"></script>
<div id="grid"></div>
<button id="next">Next generation</button>
<button id="run">Auto run</button>
<script>
</script>

Node.js

Node.js, a program that allows you to apply your JavaScript skills outside of the browser. With it, you can build anything from simple command-line tools to dynamic HTTP servers.

Background

One of the more difficult problems with writing systems that communicate over the network is managing input and output—that is, the reading and writing of data to and from the network, the hard drive, and other such devices. Moving data around takes time, and scheduling it cleverly can make a big difference in how quickly a system responds to the user or to network requests.

Node was initially conceived for the purpose of making asynchronous I/O easy and convenient.
An asynchronous interface allows the script to continue running while it does its work and calls a callback function when it’s done. This is the way Node does all its I/O.

JavaScript lends itself well to a system like Node. It is one of the few programming languages that does not have a built-in way to do I/O. Thus, JavaScript could be fit onto Node’s rather eccentric approach to I/O without ending up with two inconsistent interfaces. In 2009, when Node was being designed, people were already doing callback-based I/O in the browser, so the community around the language was used to an asynchronous programming style.

in a word, Javascript fits node very well.

Asynchronicity

In a synchronous environment, the obvious way to perform this task is to make the requests one after the other. This method has the drawback that the second request will be started only when the first has finished. The total time taken will be at least the sum of the two response times. This is not an effective use of the machine, which will be mostly idle when it is transmitting and receiving data over the network.

The solution to this problem, in a synchronous system, is to start additional threads of control.

I already touched on the fact that all those callbacks add quite a lot of noise and indirection to a program. Whether this style of asynchronicity is a good idea in general can be debated. In any case, it takes some getting used to.

But for a JavaScript-based system, I would argue that callback-style asynchronicity is a sensible choice. One of the strengths of JavaScript is its simplicity, and trying to add multiple threads of control to it would add a lot of complexity. Though callbacks don’t tend to lead to simple code, as a concept, they’re pleasantly simple yet powerful enough to write high-performance web servers.

The node command

1
2
3
4
> [-1, -2, -3].map(Math.abs)
[1, 2, 3]
> process.exit(0)
$

The process variable, just like the console variable, is available globally in Node. It provides various ways to inspect and manipulate the current program.

All the standard JavaScript global variables, such as Array, Math, and JSON, are also present in Node’s environment. Browser-related functionality, such as document and alert, is absent.

The global scope object, which is called window in the browser, has the more sensible name global in Node.

Modules

Beyond the few variables I mentioned, such as console and process, Node puts little functionality in the global scope. If you want to access other built-in functionality, you have to ask the module system for it.

The CommonJS module system, is built into Node and is used to load anything from built-in modules to downloaded libraries to files that are part of your own program.

When a string that does not look like a relative or absolute path is given to require, it is assumed to refer to either a built-in module or a module installed in a node_modules directory.

The file system module

One of the most commonly used built-in modules that comes with Node is the "fs" module, which stands for file system. This module provides functions for working with files and directories.

such as readFile

1
2
3
4
5
6
7
var fs = require("fs");
fs.readFile("file.txt", "utf8", function(error, text) {
if (error) {
throw error;
}
console.log("The file contained:", text);
});

文章标题:Eloquent JavaScript 小记

文章作者:RayJune

时间地点:上午9:01,于又玄图书馆

原始链接:http://rayjune.xyz/2017/09/21/Note-for-Eloquent-JavaScript/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。