Don’t assume you understand them until you’ve actually written a working solution.
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.
liberal 宽容的,慷慨的
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.
indispensable 不可缺少的,绝对必要的
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.
approximations 近似物
such as:
1 | 1.222 - 1.111 |
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 | console.log(typeof NaN); // 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:
1 | console.log(typeof 4.5) //number |
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 | console.log(8 * null) // 0 because null -> 0 |
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.
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.
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 themselves 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.
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 | var i; |
book’s standard solution (readability worse)
1 | for (var line = "#"; line.length < 8; line += "#") |
FizzBuzz
my solution (a little redundancy)
1 | for (var i = 0; i < 100; i++) { |
book’s standard solution (better choice)
1 | var n; |
Chess board
my solution (maybe better)
1 | var i; |
book’s standard solution (too complex)
1 | var size = 8; |
Functions
(*)Function hoisting
1 | console.log("The future says:", future()); |
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.
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 | function example() { |
(*)The call stack
Take a closer look at the way control flows through functions:
1 | function greet(who) { |
A run through this program goes roughly like this:
1 | 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.
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:
back-and-forth 反复的,来回的
1 | function chicken() { |
addition question: Browser JavaScript Stack size limit?
This is browser specific, not only the stack size, but also optimizations, things like tail recursion optimization and stuff.
I guess the only reliable thing here is to code in a way that doesn’t put tons of stuff into the stack, or manually testing(reading deep into the documentation of) each browser.
After all, when you see the “too much recursion” error or similar you already know there’s something really wrong with your code.
https://stackoverflow.com/questions/7826992/browser-JavaScript-stack-size-limit
Optional Arguments
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.
(*)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 | function wrapValue(n) { |
This is allowed and works as you’d hope—the variable can still be accessed.
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.
(*)Recursion
A function that calls itself is called recursive.
1 | function fibonacci(n) { |
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 about loop & recursion
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, 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. 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 looping. 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?
Here is a recursive solution:
1 | function findSolution(target) { |
Growing functions
There are two more or less natural ways for functions to be introduced into programs. When you:
- find yourself writing very similar code multiple times.
- 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 | function printFarmInventory(cows, chickens) { |
But if famers calls and tells us he’s also started keeping pigs, and couldn’t we please extend the software to also print pigs?
1 | function printZeroPaddedWithLabel(number, label) { |
It works! But that name, printZeroPaddedWithLabel, is a little awkward.
1 | function zeroPad(number, width) { |
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.
Exercises
Minimum
my solution (maybe better)
1 | function min(num1, num2) { |
book’s standard solution
1 | function min(a, b) { |
Recursion
my solution (almost as same as the standard solution, all good)
1 | function isEven(num) { |
book’s standard solution
1 | function isEven(n) { |
Bean counting
my solution (forget the essence of the subject, not correct)
1 | function countBs(str) { |
book’s standard solution (better)
1 | function countChar(str, ch) { |
Data Structures: Objects and Arrays
in operator
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.
object == object
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:
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 contents, but it is possible to write it yourself (which will be one of the exercises at the end of this chapter).
Exercises
The sum of a range
my solution (all good)
1 | function range(start, end, step) { |
book’s standard solution (almost same as mine)
1 | function range(start, end, step) { |
Reversing an array
my solution (use Array.reverse)
1 | function reverseArray(arr) { |
book’s standard solution (After my optimization)
1 | function reverseArray(arr) { |
A list
A list just like this:
1 | var list = { |
not accomplish
book’s standard solution (awesome)
1 | function arrayToList(array) { |
Deep comparison
my solution (incomprehensive, not good)
1 | function deepEqual(para1, para2) { |
book’s standard solution (After my optimization)
1 | function deepEqual(a, b) { |
Functional Programing
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:
1 | var total = 0; |
Which one is more likely to contain a bug?
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.
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 | function average(array) { |
This is fabulous for writing clear code. Unfortunately, this clarity comes at a cost.
fabulous 极好的
The cost
In the happy land of elegant code and pretty rainbows, there lives a spoil-sport monster called inefficiency.
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.
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, 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.
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.
Exercises
Flattening
my solution (both good)
1 | function flatten(arr) { |
book’s standard solution (as same as mine)
1 | var arrays = [[1, 2, 3], [4, 5], [6]]; |
Mother-child age difference
my solution (not understand the subject,unfinishied)
book’s standard solution
1 | function average(array) { |
Historical life expectancy
my solution (not understand the subject, unfinishied)
book’s standard solution
1 | function average(arr) { |
Every and then some
my solution (not consider forEach receive a function, so return false can’t end the outer forEach function)
1 | function every(arr, func) { |
book’s standard solution (after my optimization)
1 | function every(arr, predicate) { |
The Secret Life of Objects
History
There are several useful concepts, most importantly that of encapsulation (distinguishing between internal complexity and external interface).
This chapter describes JavaScript’s rather eccentric take on objects and the way they relate to some classical object-oriented techniques.
Constructors
A more convenient way to create objects that derive from some shared prototype is to use a constructor.
An object created with new is said to be an instance of its constructor.
Rememeber capitalize the first letter of the constructor name.
1 | function Rabbit(type) { |
(*)Object.defineProperty & object.hasOwnProperty()
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 | Object.defineProperty(Object.prototype , "hiddenNonsense", { |
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.
1 | for (var name in map) { |
(*)Object.create(null)
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 | var map = Object.create(null); |
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]”.
Polymorphic code can work with values of different shapes.
(*)Inheritance
1 | function Person(name) { |
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.
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.
Exercises
A vector type
my solution (犯了一个错误,在 prototype 上加属性的话只能加定值,不能加变量(在这里我的值就是 Math.pow(undefined) -> NaN ),而标准答案写的非常正确)
1 | function Vector(x, y) { |
book’s standard solution (Object.defineProperty)
1 | function Vector(x, y) { |
Another cell
my solution (not finish, the topic is puzzled)
book’s standard solution
1 | function StretchCell(inner, width, height) { |
Sequence interface
book’s standard solution
1 | // I am going to use a system where a sequence object has two methods: |
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
- 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 | function canYouSpotTheProblem() { |
1 | ; |
In short, putting a “use strict” at the top of your program rarely hurts and might help you spot a problem.
Testing
Fortunately, there exist pieces of software that help you build and run collections of tests (test suites) by providing a language (in the form of functions and methods) suited to expressing tests and by outputting informative information when a test fails.
These are called testing frameworks.
(*)Debugging
This is where you must resist the urge to start making random changes to the code. Instead:
- Think. Analyze what is happening and come up with a theory of why it might be happening.
- 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 supercharged return from a function:
resembles 像,类似于
- 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.
supercharged 超级有效的,增压的
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 | function promptDirection(question) { |
The throw keyword is used to raise an exception.
When the code in the try block causes an exception to be raised, the catch block is evaluated.
1 | // test1 |
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.
(*)finally: cleaning up after exceptions
Consider: After this function finishes, context restores this variable to its old value.
1 | var context = null; |
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 | function withContext(newContext, body) { |
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.
1 | try { |
Selective catching
problem
- 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 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 | function promptDirection(question) { |
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 (launch throw).
But we misspelled promptDirection, which will result in an “undefined variable” error. Because the catch block completely ignores its exception value, 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.
specific exceptions
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.
1 | try { |
But how do we recognize an exception?
Of course, we could match its message property against the error message we happen to expect.
1 | try { |
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 | function InputError(message) { |
Now promptDirection can throw such an error.
1 | function promptDirection(question) { |
And the loop can catch it more carefully.
1 | for (;;) { |
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.
(*)When use exceptions?
Use it whenever code you are running might throw an exception. Remember that you can throw your own errors* — most of the try…catch stuff I use is for catching my own exceptions.
The try-catch statement should be executed only on sections of code where you suspect errors might occur, and due to the overwhelming number of possible circumstances, you cannot completely verify if an error will take place, or when it will do so.
In the latter case, it would be appropriate to use try-catch.
Assertions
Assertions are a tool to do basic sanity checking for programmer errors. Consider:
1 | function AssertionFailed(message) { |
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 | function MultiplicatorUnitFailure() {} |
The locked box
1 | function withBoxUnlocked(body) { |
Regular Expressions
- 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.
- Testing for matches
1 | console.log(/abc/.test("abcde")); |
- Matching a set of characters
1 | console.log(/[0123456789]/.test("in 1992")); |
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
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 | var notBinary = /[^01]/; |
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 | console.log(/'\d+'/.test("'123'")); |
The star () has a similar meaning but also allows the pattern to match zero times*.
1 | console.log(/'\d*'/.test("'123'")); |
A question mark makes a part of a pattern “optional”, meaning it may occur zero or one time.
1 | var neighbor = /neighbou?r/; |
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 | var dateTime = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/; |
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 | var cartoonCrying = /boo+(hoo+)+/i; |
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 | var match = /\d+/.exec("one two 100"); |
When the regular expression contains subexpressions grouped with parentheses, the text that matched those groups will also show up in the array. The whole match is always the first element. The next element is the part matched by the first group (the one whose opening parenthesis comes first in the expression), then the second group, and so on.
1 | var quotedText = /'([^']*)'/; |
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 | console.log(/bad(ly)?/.exec("bad")); |
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 | var animalCount = /\b\d+ (pig|cow|chicken)s?\b/; |
The replace method
String values have a replace method, which can be used to replace part of the string with another string.
1 | console.log("papa".replace("p", "m")); |
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 | console.log("Borobudur".replace(/[ou]/, "a")); |
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 | console.log( |
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 | var s = "the cia and fbi"; |
Greed
change + or * to ?
1 | function stripComments(code) { |
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 | console.log(" word".search(/\S/)); |
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 | var pattern = /y/g; |
Looping over matches
1 | var input = "A string with 3 numbers in it... 42 and 88."; |
Parsing an INI file
searchengine=http://www.google.com/search?q=$1
spitefulness=9.7
1 | function parseINI(string) { |
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, such a program can be made more readable if we have a larger unit of organization.
Objects as interfaces
1 | var weekDay = function() { |
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.
Exercises
Month names
1 | var month = (function () { |
JavaScript and the Browser
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.
- First, your browser has to find out what address eloquentJavaScript.net refers to.
- 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 the tag. 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 | <p>One</p> |
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 | <p>The <img src="img/cat.png" alt="Cat"> in the |
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 | var arrayish = {0: "one", 1: "two", length: 2}; |
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 | <p data-classified="secret">The launch code is 00000000.</p> |
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 | var a = $('p'); |
Styling
“color: red; border: none”
1 | This text is displayed <strong>inline</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 | <p id="para" style="color: purple"> |
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 |
|
Elements by tag name
1 |
|
The cat’s hat
1 |
|
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 | <p>Click this document to activate the handler.</p> |
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 | <button>Act-once button</button> |
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 | <button>Click me any way you want</button> |
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 | <p>A paragraph with a <button>button</button>.</p> |
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 | <button>A</button> |
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 | <a href="https://developer.mozilla.org/">MDN</a> |
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 | var bombTimer = setTimeout(function() { |
A similar set of functions, setInterval and clearInterval are used to set timers that should repeat every X milliseconds.
1 | var ticks = 0; |
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 | <textarea>Type something here...</textarea> |
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 | <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 |
|
Mouse trail
1 |
|
Tabs
1 |
|
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 | <p>Normal HTML here.</p> |
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. The
These tags create DOM elements, just like HTML tags. For example, this changes the
1 | var circle = document.querySelector("circle"); |
The canvas element
Canvas graphics can be drawn onto a
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 | <p>Before canvas.</p> |
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 | <canvas></canvas> |
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 | <canvas></canvas> |
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 | <canvas></canvas> |
Curves
quadraticCurveTo
1 | <canvas></canvas> |
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 | <canvas></canvas> |
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 | <canvas></canvas> |
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 | <canvas></canvas> |
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 | <canvas width="200" height="200"></canvas> |
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 | <canvas></canvas> |
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 | <form method="GET" action="example/message.html"> |
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 | console.log(encodeURIComponent("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 | var req = new XMLHttpRequest(); |
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 | var req = new XMLHttpRequest(); |
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 | var req = new XMLHttpRequest(); |
Fetching XML Data
1 | <fruits> |
1 | var req = new XMLHttpRequest(); |
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 | var req = new XMLHttpRequest(); |
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 | function backgroundReadFile(url, callback) { |
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 | try { |
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 | function getURL(url, callback) { |
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 | getURL("data/nonsense.txt", function(content, error) { |
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 | function get(url) { |
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 | get("example/data.txt").then(function(text) { |
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 | function getJSON(url) { |
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 | <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 | var req = new XMLHttpRequest(); |
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 | function requestAuthor(type) { |
Waiting for multiple promises
1 | function all(promises) { |
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
1 | <p><input type="text" value="abc"> (text)</p> |
and textarea tag
1 | <textarea> |
Whenever the value of a form field changes, select fires a “change” event.
1 | <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 | <input type="text"> |
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 | <input type="text" tabindex=1> <a href=".">(help)</a> |
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 | <button>I'm all right</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
1 | <form action="example/submit.html"> |
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 | <form action="example/submit.html"> |
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 | <textarea></textarea> |
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 | <input type="text"> length: <span id="length">0</span> |
Checkboxes and radio buttons
1 | <input type="checkbox" id="teal"> |
another example
1 | Color: |
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 | <select multiple> |
1 | <select multiple> |
File fields
1 | <input type="file"> |
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 | <input type="file" multiple> |
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 | function readFile(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 | localStorage.setItem("username", "marijn"); |
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 | <textarea id="code">return "hi";</textarea> |
1 | <!doctype html> |
1 | <!doctype html> |