mirror of
https://github.com/apachecn/eloquent-js-3e-zh.git
synced 2025-05-23 20:02:20 +00:00
diff
This commit is contained in:
parent
25592d43f2
commit
45c2df504c
652
diff-en/2ech11-3ech12.diff
Normal file
652
diff-en/2ech11-3ech12.diff
Normal file
@ -0,0 +1,652 @@
|
||||
diff --git a/2ech11.md b/3ech12.md
|
||||
index 4a9b140..3167c30 100644
|
||||
--- a/2ech11.md
|
||||
+++ b/3ech12.md
|
||||
@@ -1,26 +1,22 @@
|
||||
-# Chapter 11Project: A Programming Language
|
||||
+# Chapter 12Project: A Programming Language
|
||||
|
||||
> The evaluator, which determines the meaning of expressions in a programming language, is just another program.
|
||||
>
|
||||
> <footer>Hal Abelson and Gerald Sussman, <cite>Structure and Interpretation of Computer Programs</cite></footer>
|
||||
|
||||
-> When a student asked the master about the nature of the cycle of Data and Control, Yuan-Ma replied ‘Think of a compiler, compiling itself.'
|
||||
->
|
||||
-> <footer>Master Yuan-Ma, <cite>The Book of Programming</cite></footer>
|
||||
-
|
||||
Building your own programming language is surprisingly easy (as long as you do not aim too high) and very enlightening.
|
||||
|
||||
-The main thing I want to show in this chapter is that there is no magic involved in building your own language. I've often felt that some human inventions were so immensely clever and complicated that I'd never be able to understand them. But with a little reading and tinkering, such things often turn out to be quite mundane.
|
||||
+The main thing I want to show in this chapter is that there is no magic involved in building your own language. I've often felt that some human inventions were so immensely clever and complicated that I'd never be able to understand them. But with a little reading and experimenting, they often turn out to be quite mundane.
|
||||
|
||||
-We will build a programming language called Egg. It will be a tiny, simple language but one that is powerful enough to express any computation you can think of. It will also allow simple abstraction based on functions.
|
||||
+We will build a programming language called Egg. It will be a tiny, simple language—but one that is powerful enough to express any computation you can think of. It will allow simple abstraction based on functions.
|
||||
|
||||
## Parsing
|
||||
|
||||
-The most immediately visible part of a programming language is its _syntax_, or notation. A _parser_ is a program that reads a piece of text and produces a data structure that reflects the structure of the program contained in that text. If the text does not form a valid program, the parser should complain and point out the error.
|
||||
+The most immediately visible part of a programming language is its _syntax_, or notation. A _parser_ is a program that reads a piece of text and produces a data structure that reflects the structure of the program contained in that text. If the text does not form a valid program, the parser should point out the error.
|
||||
|
||||
-Our language will have a simple and uniform syntax. Everything in Egg is an expression. An expression can be a variable, a number, a string, or an _application_. Applications are used for function calls but also for constructs such as `if` or `while`.
|
||||
+Our language will have a simple and uniform syntax. Everything in Egg is an expression. An expression can be the name of a binding, a number, a string, or an _application_. Applications are used for function calls but also for constructs such as `if` or `while`.
|
||||
|
||||
-To keep the parser simple, strings in Egg do not support anything like backslash escapes. A string is simply a sequence of characters that are not double quotes, wrapped in double quotes. A number is a sequence of digits. Variable names can consist of any character that is not whitespace and does not have a special meaning in the syntax.
|
||||
+To keep the parser simple, strings in Egg do not support anything like backslash escapes. A string is simply a sequence of characters that are not double quotes, wrapped in double quotes. A number is a sequence of digits. Binding names can consist of any character that is not whitespace and that does not have a special meaning in the syntax.
|
||||
|
||||
Applications are written the way they are in JavaScript, by putting parentheses after an expression and having any number of arguments between those parentheses, separated by commas.
|
||||
|
||||
@@ -31,11 +27,11 @@ do(define(x, 10),
|
||||
print("small")))
|
||||
```
|
||||
|
||||
-The uniformity of the Egg language means that things that are operators in JavaScript (such as `>`) are normal variables in this language, applied just like other functions. And since the syntax has no concept of a block, we need a `do` construct to represent doing multiple things in sequence.
|
||||
+The uniformity of the Egg language means that things that are operators in JavaScript (such as `>`) are normal bindings in this language, applied just like other functions. And since the syntax has no concept of a block, we need a `do` construct to represent doing multiple things in sequence.
|
||||
|
||||
-The data structure that the parser will use to describe a program will consist of expression objects, each of which has a `type` property indicating the kind of expression it is and other properties to describe its content.
|
||||
+The data structure that the parser will use to describe a program consists of expression objects, each of which has a `type` property indicating the kind of expression it is and other properties to describe its content.
|
||||
|
||||
-Expressions of type `"value"` represent literal strings or numbers. Their `value` property contains the string or number value that they represent. Expressions of type `"word"` are used for identifiers (names). Such objects have a `name` property that holds the identifier's name as a string. Finally, `"apply"` expressions represent applications. They have an `operator` property that refers to the expression that is being applied, and they have an `args` property that refers to an array of argument expressions.
|
||||
+Expressions of type `"value"` represent literal strings or numbers. Their `value` property contains the string or number value that they represent. Expressions of type `"word"` are used for identifiers (names). Such objects have a `name` property that holds the identifier's name as a string. Finally, `"apply"` expressions represent applications. They have an `operator` property that refers to the expression that is being applied, and an `args` property that holds an array of argument expressions.
|
||||
|
||||
The `>(x, 5)` part of the previous program would be represented like this:
|
||||
|
||||
@@ -50,15 +46,15 @@ The `>(x, 5)` part of the previous program would be represented like this:
|
||||
}
|
||||
```
|
||||
|
||||
-Such a data structure is called a _syntax tree_. If you imagine the objects as dots and the links between them as lines between those dots, it has a treelike shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way branches split and split again.
|
||||
+Such a data structure is called a _syntax tree_. If you imagine the objects as dots and the links between them as lines between those dots, it has a treelike shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way tree branches split and split again.
|
||||
|
||||
-
|
||||
+<figure></figure>
|
||||
|
||||
Contrast this to the parser we wrote for the configuration file format in [Chapter 9](09_regexp.html#ini), which had a simple structure: it split the input into lines and handled those lines one at a time. There were only a few simple forms that a line was allowed to have.
|
||||
|
||||
Here we must find a different approach. Expressions are not separated into lines, and they have a recursive structure. Application expressions _contain_ other expressions.
|
||||
|
||||
-Fortunately, this problem can be solved elegantly by writing a parser function that is recursive in a way that reflects the recursive nature of the language.
|
||||
+Fortunately, this problem can be solved very well by writing a parser function that is recursive in a way that reflects the recursive nature of the language.
|
||||
|
||||
We define a function `parseExpression`, which takes a string as input and returns an object containing the data structure for the expression at the start of the string, along with the part of the string left after parsing this expression. When parsing subexpressions (the argument to an application, for example), this function can be called again, yielding the argument expression as well as the text that remains. This text may in turn contain more arguments or may be the closing parenthesis that ends the list of arguments.
|
||||
|
||||
@@ -67,54 +63,57 @@ This is the first part of the parser:
|
||||
```
|
||||
function parseExpression(program) {
|
||||
program = skipSpace(program);
|
||||
- var match, expr;
|
||||
- if (match = /^"([^"]*)"/.exec(program))
|
||||
+ let match, expr;
|
||||
+ if (match = /^"([^"]*)"/.exec(program)) {
|
||||
expr = {type: "value", value: match[1]};
|
||||
- else if (match = /^\d+\b/.exec(program))
|
||||
+ } else if (match = /^\d+\b/.exec(program)) {
|
||||
expr = {type: "value", value: Number(match[0])};
|
||||
- else if (match = /^[^\s(),"]+/.exec(program))
|
||||
+ } else if (match = /^[^\s(),"]+/.exec(program)) {
|
||||
expr = {type: "word", name: match[0]};
|
||||
- else
|
||||
+ } else {
|
||||
throw new SyntaxError("Unexpected syntax: " + program);
|
||||
+ }
|
||||
|
||||
return parseApply(expr, program.slice(match[0].length));
|
||||
}
|
||||
|
||||
function skipSpace(string) {
|
||||
- var first = string.search(/\S/);
|
||||
+ let first = string.search(/\S/);
|
||||
if (first == -1) return "";
|
||||
return string.slice(first);
|
||||
}
|
||||
```
|
||||
|
||||
-Because Egg allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. This is what the `skipSpace` function helps with.
|
||||
+Because Egg, like JavaScript, allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. That is what the `skipSpace` function helps with.
|
||||
|
||||
-After skipping any leading space, `parseExpression` uses three regular expressions to spot the three simple (atomic) elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which one matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. `SyntaxError` is a standard error object type, which is raised when an attempt is made to run an invalid JavaScript program.
|
||||
+After skipping any leading space, `parseExpression` uses three regular expressions to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which one matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use `SyntaxError` instead of `Error` as exception constructor, which is another standard error type, because it is a little more specific—it is also the error type thrown when an attempt is made to run an invalid JavaScript program.
|
||||
|
||||
-We can then cut off the part that we matched from the program string and pass that, along with the object for the expression, to `parseApply`, which checks whether the expression is an application. If so, it parses a parenthesized list of arguments.
|
||||
+We then cut off the part that was matched from the program string and pass that, along with the object for the expression, to `parseApply`, which checks whether the expression is an application. If so, it parses a parenthesized list of arguments.
|
||||
|
||||
```
|
||||
function parseApply(expr, program) {
|
||||
program = skipSpace(program);
|
||||
- if (program[0] != "(")
|
||||
+ if (program[0] != "(") {
|
||||
return {expr: expr, rest: program};
|
||||
+ }
|
||||
|
||||
program = skipSpace(program.slice(1));
|
||||
expr = {type: "apply", operator: expr, args: []};
|
||||
while (program[0] != ")") {
|
||||
- var arg = parseExpression(program);
|
||||
+ let arg = parseExpression(program);
|
||||
expr.args.push(arg.expr);
|
||||
program = skipSpace(arg.rest);
|
||||
- if (program[0] == ",")
|
||||
+ if (program[0] == ",") {
|
||||
program = skipSpace(program.slice(1));
|
||||
- else if (program[0] != ")")
|
||||
+ } else if (program[0] != ")") {
|
||||
throw new SyntaxError("Expected ',' or ')'");
|
||||
+ }
|
||||
}
|
||||
return parseApply(expr, program.slice(1));
|
||||
}
|
||||
```
|
||||
|
||||
-If the next character in the program is not an opening parenthesis, this is not an application, and `parseApply` simply returns the expression it was given.
|
||||
+If the next character in the program is not an opening parenthesis, this is not an application, and `parseApply` returns the expression it was given.
|
||||
|
||||
Otherwise, it skips the opening parenthesis and creates the syntax tree object for this application expression. It then recursively calls `parseExpression` to parse each argument until a closing parenthesis is found. The recursion is indirect, through `parseApply` and `parseExpression` calling each other.
|
||||
|
||||
@@ -124,10 +123,11 @@ This is all we need to parse Egg. We wrap it in a convenient `parse` function th
|
||||
|
||||
```
|
||||
function parse(program) {
|
||||
- var result = parseExpression(program);
|
||||
- if (skipSpace(result.rest).length > 0)
|
||||
+ let {expr, rest} = parseExpression(program);
|
||||
+ if (skipSpace(rest).length > 0) {
|
||||
throw new SyntaxError("Unexpected text after program");
|
||||
- return result.expr;
|
||||
+ }
|
||||
+ return expr;
|
||||
}
|
||||
|
||||
console.log(parse("+(a, 10)"));
|
||||
@@ -141,78 +141,80 @@ It works! It doesn't give us very helpful information when it fails and doesn't
|
||||
|
||||
## The evaluator
|
||||
|
||||
-What can we do with the syntax tree for a program? Run it, of course! And that is what the evaluator does. You give it a syntax tree and an environment object that associates names with values, and it will evaluate the expression that the tree represents and return the value that this produces.
|
||||
-
|
||||
-```
|
||||
-function evaluate(expr, env) {
|
||||
- switch(expr.type) {
|
||||
- case "value":
|
||||
- return expr.value;
|
||||
-
|
||||
- case "word":
|
||||
- if (expr.name in env)
|
||||
- return env[expr.name];
|
||||
- else
|
||||
- throw new ReferenceError("Undefined variable: " +
|
||||
- expr.name);
|
||||
- case "apply":
|
||||
- if (expr.operator.type == "word" &&
|
||||
- expr.operator.name in specialForms)
|
||||
- return specialForms[expr.operator.name](expr.args,
|
||||
- env);
|
||||
- var op = evaluate(expr.operator, env);
|
||||
- if (typeof op != "function")
|
||||
+What can we do with the syntax tree for a program? Run it, of course! And that is what the evaluator does. You give it a syntax tree and a scope object that associates names with values, and it will evaluate the expression that the tree represents and return the value that this produces.
|
||||
+
|
||||
+```
|
||||
+const specialForms = Object.create(null);
|
||||
+
|
||||
+function evaluate(expr, scope) {
|
||||
+ if (expr.type == "value") {
|
||||
+ return expr.value;
|
||||
+ } else if (expr.type == "word") {
|
||||
+ if (expr.name in scope) {
|
||||
+ return scope[expr.name];
|
||||
+ } else {
|
||||
+ throw new ReferenceError(
|
||||
+ `Undefined binding: ${expr.name}`);
|
||||
+ }
|
||||
+ } else if (expr.type == "apply") {
|
||||
+ let {operator, args} = expr;
|
||||
+ if (operator.type == "word" &&
|
||||
+ operator.name in specialForms) {
|
||||
+ return specialForms[operator.name](expr.args, scope);
|
||||
+ } else {
|
||||
+ let op = evaluate(operator, scope);
|
||||
+ if (typeof op == "function") {
|
||||
+ return op(...args.map(arg => evaluate(arg, scope)));
|
||||
+ } else {
|
||||
throw new TypeError("Applying a non-function.");
|
||||
- return op.apply(null, expr.args.map(function(arg) {
|
||||
- return evaluate(arg, env);
|
||||
- }));
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
}
|
||||
-
|
||||
-var specialForms = Object.create(null);
|
||||
```
|
||||
|
||||
-The evaluator has code for each of the expression types. A literal value expression simply produces its value. (For example, the expression `100` just evaluates to the number 100.) For a variable, we must check whether it is actually defined in the environment and, if it is, fetch the variable's value.
|
||||
+The evaluator has code for each of the expression types. A literal value expression produces its value. (For example, the expression `100` just evaluates to the number 100.) For a binding, we must check whether it is actually defined in the scope and, if it is, fetch the binding's value.
|
||||
|
||||
-Applications are more involved. If they are a special form, like `if`, we do not evaluate anything and simply pass the argument expressions, along with the environment, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the result of evaluating the arguments.
|
||||
+Applications are more involved. If they are a special form, like `if`, we do not evaluate anything and pass the argument expressions, along with the scope, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the evaluated arguments.
|
||||
|
||||
-We will use plain JavaScript function values to represent Egg's function values. We will come back to this [later](11_language.html#egg_fun), when the special form called `fun` is defined.
|
||||
+We use plain JavaScript function values to represent Egg's function values. We will come back to this [later](12_language.html#egg_fun), when the special form called `fun` is defined.
|
||||
|
||||
-The recursive structure of `evaluate` resembles the similar structure of the parser. Both mirror the structure of the language itself. It would also be possible to integrate the parser with the evaluator and evaluate during parsing, but splitting them up this way makes the program more readable.
|
||||
+The recursive structure of `evaluate` resembles the similar structure of the parser, and both mirror the structure of the language itself. It would also be possible to integrate the parser with the evaluator and evaluate during parsing, but splitting them up this way makes the program clearer.
|
||||
|
||||
-This is really all that is needed to interpret Egg. It is that simple. But without defining a few special forms and adding some useful values to the environment, you can't do anything with this language yet.
|
||||
+This is really all that is needed to interpret Egg. It is that simple. But without defining a few special forms and adding some useful values to the environment, you can't do much with this language yet.
|
||||
|
||||
## Special forms
|
||||
|
||||
-The `specialForms` object is used to define special syntax in Egg. It associates words with functions that evaluate such special forms. It is currently empty. Let's add some forms.
|
||||
+The `specialForms` object is used to define special syntax in Egg. It associates words with functions that evaluate such forms. It is currently empty. Let's add `if`.
|
||||
|
||||
```
|
||||
-specialForms["if"] = function(args, env) {
|
||||
- if (args.length != 3)
|
||||
- throw new SyntaxError("Bad number of args to if");
|
||||
-
|
||||
- if (evaluate(args[0], env) !== false)
|
||||
- return evaluate(args[1], env);
|
||||
- else
|
||||
- return evaluate(args[2], env);
|
||||
+specialForms.if = (args, scope) => {
|
||||
+ if (args.length != 3) {
|
||||
+ throw new SyntaxError("Wrong number of args to if");
|
||||
+ } else if (evaluate(args[0], scope) !== false) {
|
||||
+ return evaluate(args[1], scope);
|
||||
+ } else {
|
||||
+ return evaluate(args[2], scope);
|
||||
+ }
|
||||
};
|
||||
```
|
||||
|
||||
-Egg's `if` construct expects exactly three arguments. It will evaluate the first, and if the result isn't the value `false`, it will evaluate the second. Otherwise, the third gets evaluated. This `if` form is more similar to JavaScript's ternary `?:` operator than to JavaScript's `if`. It is an expression, not a statement, and it produces a value, namely, the result of the second or third argument.
|
||||
+Egg's `if` construct expects exactly three arguments. It will evaluate the first, and if the result isn't the value `false`, it will evaluate the second. Otherwise, the third gets evaluated. This `if` form is more similar to JavaScript's ternary `?:` operator than to JavaScript's `if`. It is an expression, not a statement, and it produces a value, namely the result of the second or third argument.
|
||||
|
||||
-Egg differs from JavaScript in how it handles the condition value to `if`. It will not treat things like zero or the empty string as false, but only the precise value `false`.
|
||||
+Egg also differs from JavaScript in how it handles the condition value to `if`. It will not treat things like zero or the empty string as false, only the precise value `false`.
|
||||
|
||||
The reason we need to represent `if` as a special form, rather than a regular function, is that all arguments to functions are evaluated before the function is called, whereas `if` should evaluate only _either_ its second or its third argument, depending on the value of the first.
|
||||
|
||||
The `while` form is similar.
|
||||
|
||||
```
|
||||
-specialForms["while"] = function(args, env) {
|
||||
- if (args.length != 2)
|
||||
- throw new SyntaxError("Bad number of args to while");
|
||||
-
|
||||
- while (evaluate(args[0], env) !== false)
|
||||
- evaluate(args[1], env);
|
||||
+specialForms.while = (args, scope) => {
|
||||
+ if (args.length != 2) {
|
||||
+ throw new SyntaxError("Wrong number of args to while");
|
||||
+ }
|
||||
+ while (evaluate(args[0], scope) !== false) {
|
||||
+ evaluate(args[1], scope);
|
||||
+ }
|
||||
|
||||
// Since undefined does not exist in Egg, we return false,
|
||||
// for lack of a meaningful result.
|
||||
@@ -223,133 +225,138 @@ specialForms["while"] = function(args, env) {
|
||||
Another basic building block is `do`, which executes all its arguments from top to bottom. Its value is the value produced by the last argument.
|
||||
|
||||
```
|
||||
-specialForms["do"] = function(args, env) {
|
||||
- var value = false;
|
||||
- args.forEach(function(arg) {
|
||||
- value = evaluate(arg, env);
|
||||
- });
|
||||
+specialForms.do = (args, scope) => {
|
||||
+ let value = false;
|
||||
+ for (let arg of args) {
|
||||
+ value = evaluate(arg, scope);
|
||||
+ }
|
||||
return value;
|
||||
};
|
||||
```
|
||||
|
||||
-To be able to create variables and give them new values, we also create a form called `define`. It expects a word as its first argument and an expression producing the value to assign to that word as its second argument. Since `define`, like everything, is an expression, it must return a value. We'll make it return the value that was assigned (just like JavaScript's `=` operator).
|
||||
+To be able to create bindings and give them new values, we also create a form called `define`. It expects a word as its first argument and an expression producing the value to assign to that word as its second argument. Since `define`, like everything, is an expression, it must return a value. We'll make it return the value that was assigned (just like JavaScript's `=` operator).
|
||||
|
||||
```
|
||||
-specialForms["define"] = function(args, env) {
|
||||
- if (args.length != 2 || args[0].type != "word")
|
||||
- throw new SyntaxError("Bad use of define");
|
||||
- var value = evaluate(args[1], env);
|
||||
- env[args[0].name] = value;
|
||||
+specialForms.define = (args, scope) => {
|
||||
+ if (args.length != 2 || args[0].type != "word") {
|
||||
+ throw new SyntaxError("Incorrect use of define");
|
||||
+ }
|
||||
+ let value = evaluate(args[1], scope);
|
||||
+ scope[args[0].name] = value;
|
||||
return value;
|
||||
};
|
||||
```
|
||||
|
||||
## The environment
|
||||
|
||||
-The environment accepted by `evaluate` is an object with properties whose names correspond to variable names and whose values correspond to the values those variables are bound to. Let's define an environment object to represent the global scope.
|
||||
+The scope accepted by `evaluate` is an object with properties whose names correspond to binding names and whose values correspond to the values those bindings are bound to. Let's define an object to represent the global scope.
|
||||
|
||||
-To be able to use the `if` construct we just defined, we must have access to Boolean values. Since there are only two Boolean values, we do not need special syntax for them. We simply bind two variables to the values `true` and `false` and use those.
|
||||
+To be able to use the `if` construct we just defined, we must have access to Boolean values. Since there are only two Boolean values, we do not need special syntax for them. We simply bind two names to the values `true` and `false` and use those.
|
||||
|
||||
```
|
||||
-var topEnv = Object.create(null);
|
||||
+const topScope = Object.create(null);
|
||||
|
||||
-topEnv["true"] = true;
|
||||
-topEnv["false"] = false;
|
||||
+topScope.true = true;
|
||||
+topScope.false = false;
|
||||
```
|
||||
|
||||
We can now evaluate a simple expression that negates a Boolean value.
|
||||
|
||||
```
|
||||
-var prog = parse("if(true, false, true)");
|
||||
-console.log(evaluate(prog, topEnv));
|
||||
+let prog = parse(`if(true, false, true)`);
|
||||
+console.log(evaluate(prog, topScope));
|
||||
// → false
|
||||
```
|
||||
|
||||
-To supply basic arithmetic and comparison operators, we will also add some function values to the environment. In the interest of keeping the code short, we'll use `new Function` to synthesize a bunch of operator functions in a loop, rather than defining them all individually.
|
||||
+To supply basic arithmetic and comparison operators, we will also add some function values to the scope. In the interest of keeping the code short, we'll use `Function` to synthesize a bunch of operator functions in a loop, instead of defining them individually.
|
||||
|
||||
```
|
||||
-["+", "-", "*", "/", "==", "<", ">"].forEach(function(op) {
|
||||
- topEnv[op] = new Function("a, b", "return a " + op + " b;");
|
||||
-});
|
||||
+for (let op of ["+", "-", "*", "/", "==", "<", ">"]) {
|
||||
+ topScope[op] = Function("a, b", `return a ${op} b;`);
|
||||
+}
|
||||
```
|
||||
|
||||
A way to output values is also very useful, so we'll wrap `console.log` in a function and call it `print`.
|
||||
|
||||
```
|
||||
-topEnv["print"] = function(value) {
|
||||
+topScope.print = value => {
|
||||
console.log(value);
|
||||
return value;
|
||||
};
|
||||
```
|
||||
|
||||
-That gives us enough elementary tools to write simple programs. The following `run` function provides a convenient way to write and run them. It creates a fresh environment and parses and evaluates the strings we give it as a single program.
|
||||
+That gives us enough elementary tools to write simple programs. The following function provides a convenient way to parse a program and run it in a fresh scope.
|
||||
|
||||
```
|
||||
-function run() {
|
||||
- var env = Object.create(topEnv);
|
||||
- var program = Array.prototype.slice
|
||||
- .call(arguments, 0).join("\n");
|
||||
- return evaluate(parse(program), env);
|
||||
+function run(program) {
|
||||
+ return evaluate(parse(program), Object.create(topScope));
|
||||
}
|
||||
```
|
||||
|
||||
-The use of `Array.prototype.slice.call` is a trick to turn an array-like object, such as `arguments`, into a real array so that we can call `join` on it. It takes all the arguments given to `run` and treats them as the lines of a program.
|
||||
+We'll use object prototype chains to represent nested scopes, so that the program can add bindings to its local scope without changing the top-level scope.
|
||||
|
||||
```
|
||||
-run("do(define(total, 0),",
|
||||
- " define(count, 1),",
|
||||
- " while(<(count, 11),",
|
||||
- " do(define(total, +(total, count)),",
|
||||
- " define(count, +(count, 1)))),",
|
||||
- " print(total))");
|
||||
+run(`
|
||||
+do(define(total, 0),
|
||||
+ define(count, 1),
|
||||
+ while(<(count, 11),
|
||||
+ do(define(total, +(total, count)),
|
||||
+ define(count, +(count, 1)))),
|
||||
+ print(total))
|
||||
+`);
|
||||
// → 55
|
||||
```
|
||||
|
||||
-This is the program we've seen several times before, which computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program but not bad for a language implemented in less than 150 lines of code.
|
||||
+This is the program we've seen several times before, which computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in less than 150 lines of code.
|
||||
|
||||
## Functions
|
||||
|
||||
A programming language without functions is a poor programming language indeed.
|
||||
|
||||
-Fortunately, it is not hard to add a `fun` construct, which treats its last argument as the function's body and treats all the arguments before that as the names of the function's arguments.
|
||||
+Fortunately, it isn't hard to add a `fun` construct, which treats its last argument as the function's body and uses all arguments before that as the names of the function's parameters.
|
||||
|
||||
```
|
||||
-specialForms["fun"] = function(args, env) {
|
||||
- if (!args.length)
|
||||
+specialForms.fun = (args, scope) => {
|
||||
+ if (!args.length) {
|
||||
throw new SyntaxError("Functions need a body");
|
||||
- function name(expr) {
|
||||
- if (expr.type != "word")
|
||||
- throw new SyntaxError("Arg names must be words");
|
||||
- return expr.name;
|
||||
}
|
||||
- var argNames = args.slice(0, args.length - 1).map(name);
|
||||
- var body = args[args.length - 1];
|
||||
+ let body = args[args.length - 1];
|
||||
+ let params = args.slice(0, args.length - 1).map(expr => {
|
||||
+ if (expr.type != "word") {
|
||||
+ throw new SyntaxError("Parameter names must be words");
|
||||
+ }
|
||||
+ return expr.name;
|
||||
+ });
|
||||
|
||||
return function() {
|
||||
- if (arguments.length != argNames.length)
|
||||
+ if (arguments.length != params.length) {
|
||||
throw new TypeError("Wrong number of arguments");
|
||||
- var localEnv = Object.create(env);
|
||||
- for (var i = 0; i < arguments.length; i++)
|
||||
- localEnv[argNames[i]] = arguments[i];
|
||||
- return evaluate(body, localEnv);
|
||||
+ }
|
||||
+ let localScope = Object.create(scope);
|
||||
+ for (let i = 0; i < arguments.length; i++) {
|
||||
+ localScope[params[i]] = arguments[i];
|
||||
+ }
|
||||
+ return evaluate(body, localScope);
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
-Functions in Egg have their own local environment, just like in JavaScript. We use `Object.create` to make a new object that has access to the variables in the outer environment (its prototype) but that can also contain new variables without modifying that outer scope.
|
||||
-
|
||||
-The function created by the `fun` form creates this local environment and adds the argument variables to it. It then evaluates the function body in this environment and returns the result.
|
||||
+Functions in Egg get their own local scope. The function produced by the `fun` form creates this local scope and adds the argument bindings to it. It then evaluates the function body in this scope and returns the result.
|
||||
|
||||
```
|
||||
-run("do(define(plusOne, fun(a, +(a, 1))),",
|
||||
- " print(plusOne(10)))");
|
||||
+run(`
|
||||
+do(define(plusOne, fun(a, +(a, 1))),
|
||||
+ print(plusOne(10)))
|
||||
+`);
|
||||
// → 11
|
||||
|
||||
-run("do(define(pow, fun(base, exp,",
|
||||
- " if(==(exp, 0),",
|
||||
- " 1,",
|
||||
- " *(base, pow(base, -(exp, 1)))))),",
|
||||
- " print(pow(2, 10)))");
|
||||
+run(`
|
||||
+do(define(pow, fun(base, exp,
|
||||
+ if(==(exp, 0),
|
||||
+ 1,
|
||||
+ *(base, pow(base, -(exp, 1)))))),
|
||||
+ print(pow(2, 10)))
|
||||
+`);
|
||||
// → 1024
|
||||
```
|
||||
|
||||
@@ -357,11 +364,11 @@ run("do(define(pow, fun(base, exp,",
|
||||
|
||||
What we have built is an interpreter. During evaluation, it acts directly on the representation of the program produced by the parser.
|
||||
|
||||
-_Compilation_ is the process of adding another step between the parsing and the running of a program, which transforms the program into something that can be evaluated more efficiently by doing as much work as possible in advance. For example, in well-designed languages it is obvious, for each use of a variable, which variable is being referred to, without actually running the program. This can be used to avoid looking up the variable by name every time it is accessed and to directly fetch it from some predetermined memory location.
|
||||
+_Compilation_ is the process of adding another step between the parsing and the running of a program, which transforms the program into something that can be evaluated more efficiently by doing as much work as possible in advance. For example, in well-designed languages it is obvious, for each use of a binding, which binding is being referred to, without actually running the program. This can be used to avoid looking up the binding by name every time it is accessed, instead directly fetching it from some predetermined memory location.
|
||||
|
||||
Traditionally, compilation involves converting the program to machine code, the raw format that a computer's processor can execute. But any process that converts a program to a different representation can be thought of as compilation.
|
||||
|
||||
-It would be possible to write an alternative evaluation strategy for Egg, one that first converts the program to a JavaScript program, uses `new Function` to invoke the JavaScript compiler on it, and then runs the result. When done right, this would make Egg run very fast while still being quite simple to implement.
|
||||
+It would be possible to write an alternative evaluation strategy for Egg, one that first converts the program to a JavaScript program, uses `Function` to invoke the JavaScript compiler on it, and then runs the result. When done right, this would make Egg run very fast while still being quite simple to implement.
|
||||
|
||||
If you are interested in this topic and willing to spend some time on it, I encourage you to try to implement such a compiler as an exercise.
|
||||
|
||||
@@ -373,7 +380,7 @@ If you compare the implementation of Egg, built on top of JavaScript, with the a
|
||||
|
||||
And when it comes to getting something done, cheating is more effective than doing everything yourself. Though the toy language in this chapter doesn't do anything that couldn't be done better in JavaScript, there _are_ situations where writing small languages helps get real work done.
|
||||
|
||||
-Such a language does not have to resemble a typical programming language. If JavaScript didn't come equipped with regular expressions, you could write your own parser and evaluator for such a sublanguage.
|
||||
+Such a language does not have to resemble a typical programming language. If JavaScript didn't come equipped with regular expressions, for example, you could write your own parser and evaluator for regular expressions.
|
||||
|
||||
Or imagine you are building a giant robotic dinosaur and need to program its behavior. JavaScript might not be the most effective way to do this. You might instead opt for a language that looks like this:
|
||||
|
||||
@@ -393,66 +400,70 @@ behavior attack
|
||||
launch arm-rockets
|
||||
```
|
||||
|
||||
-This is what is usually called a _domain-specific language_, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to express exactly the things that need expressing in its domain and nothing else.
|
||||
+This is what is usually called a _domain-specific language_, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to describe exactly the things that need to be described in its domain, and nothing else.
|
||||
|
||||
## Exercises
|
||||
|
||||
### Arrays
|
||||
|
||||
-Add support for arrays to Egg by adding the following three functions to the top scope: `array(...)` to construct an array containing the argument values, `length(array)` to get an array's length, and `element(array, n)` to fetch the n<sup>th</sup> element from an array.
|
||||
+Add support for arrays to Egg by adding the following three functions to the top scope: `array(...values)` to construct an array containing the argument values, `length(array)` to get an array's length, and `element(array, n)` to fetch the n<sup>th</sup> element from an array.
|
||||
|
||||
```
|
||||
// Modify these definitions...
|
||||
|
||||
-topEnv["array"] = "...";
|
||||
+topScope.array = "...";
|
||||
|
||||
-topEnv["length"] = "...";
|
||||
+topScope.length = "...";
|
||||
|
||||
-topEnv["element"] = "...";
|
||||
+topScope.element = "...";
|
||||
|
||||
-run("do(define(sum, fun(array,",
|
||||
- " do(define(i, 0),",
|
||||
- " define(sum, 0),",
|
||||
- " while(<(i, length(array)),",
|
||||
- " do(define(sum, +(sum, element(array, i))),",
|
||||
- " define(i, +(i, 1)))),",
|
||||
- " sum))),",
|
||||
- " print(sum(array(1, 2, 3))))");
|
||||
+run(`
|
||||
+do(define(sum, fun(array,
|
||||
+ do(define(i, 0),
|
||||
+ define(sum, 0),
|
||||
+ while(<(i, length(array)),
|
||||
+ do(define(sum, +(sum, element(array, i))),
|
||||
+ define(i, +(i, 1)))),
|
||||
+ sum))),
|
||||
+ print(sum(array(1, 2, 3))))
|
||||
+`);
|
||||
// → 6
|
||||
```
|
||||
|
||||
The easiest way to do this is to represent Egg arrays with JavaScript arrays.
|
||||
|
||||
-The values added to the top environment must be functions. `Array.prototype.slice` can be used to convert an `arguments` array-like object into a regular array.
|
||||
+The values added to the top scope must be functions. By using a rest argument (with triple-dot notation), the definition of `array` can be _very_ simple.
|
||||
|
||||
### Closure
|
||||
|
||||
-The way we have defined `fun` allows functions in Egg to “close over” the surrounding environment, allowing the function's body to use local values that were visible at the time the function was defined, just like JavaScript functions do.
|
||||
+The way we have defined `fun` allows functions in Egg to reference the surrounding scope, allowing the function's body to use local values that were visible at the time the function was defined, just like JavaScript functions do.
|
||||
|
||||
-The following program illustrates this: function `f` returns a function that adds its argument to `f`'s argument, meaning that it needs access to the local scope inside `f` to be able to use variable `a`.
|
||||
+The following program illustrates this: function `f` returns a function that adds its argument to `f`'s argument, meaning that it needs access to the local scope inside `f` to be able to use binding `a`.
|
||||
|
||||
```
|
||||
-run("do(define(f, fun(a, fun(b, +(a, b)))),",
|
||||
- " print(f(4)(5)))");
|
||||
+run(`
|
||||
+do(define(f, fun(a, fun(b, +(a, b)))),
|
||||
+ print(f(4)(5)))
|
||||
+`);
|
||||
// → 9
|
||||
```
|
||||
|
||||
Go back to the definition of the `fun` form and explain which mechanism causes this to work.
|
||||
|
||||
-Again, we are riding along on a JavaScript mechanism to get the equivalent feature in Egg. Special forms are passed the local environment in which they are evaluated so that they can evaluate their subforms in that environment. The function returned by `fun` closes over the `env` argument given to its enclosing function and uses that to create the function's local environment when it is called.
|
||||
+Again, we are riding along on a JavaScript mechanism to get the equivalent feature in Egg. Special forms are passed the local scope in which they are evaluated so that they can evaluate their subforms in that scope. The function returned by `fun` has access to the `scope` argument given to its enclosing function and uses that to create the function's local scope when it is called.
|
||||
|
||||
-This means that the prototype of the local environment will be the environment in which the function was created, which makes it possible to access variables in that environment from the function. This is all there is to implementing closure (though to compile it in a way that is actually efficient, you'd need to do some more work).
|
||||
+This means that the prototype of the local scope will be the scope in which the function was created, which makes it possible to access bindings in that scope from the function. This is all there is to implementing closure (though to compile it in a way that is actually efficient, you'd need to do some more work).
|
||||
|
||||
### Comments
|
||||
|
||||
It would be nice if we could write comments in Egg. For example, whenever we find a hash sign (`#`), we could treat the rest of the line as a comment and ignore it, similar to `//` in JavaScript.
|
||||
|
||||
-We do not have to make any big changes to the parser to support this. We can simply change `skipSpace` to skip comments like they are whitespace so that all the points where `skipSpace` is called will now also skip comments. Make this change.
|
||||
+We do not have to make any big changes to the parser to support this. We can simply change `skipSpace` to skip comments as if they are whitespace so that all the points where `skipSpace` is called will now also skip comments. Make this change.
|
||||
|
||||
```
|
||||
// This is the old skipSpace. Modify it...
|
||||
function skipSpace(string) {
|
||||
- var first = string.search(/\S/);
|
||||
+ let first = string.search(/\S/);
|
||||
if (first == -1) return "";
|
||||
return string.slice(first);
|
||||
}
|
||||
@@ -472,34 +483,34 @@ A regular expression is probably the easiest way to solve this. Write something
|
||||
|
||||
### Fixing scope
|
||||
|
||||
-Currently, the only way to assign a variable a value is `define`. This construct acts as a way both to define new variables and to give existing ones a new value.
|
||||
+Currently, the only way to assign a binding a value is `define`. This construct acts as a way both to define new bindings and to give existing ones a new value.
|
||||
|
||||
-This ambiguity causes a problem. When you try to give a nonlocal variable a new value, you will end up defining a local one with the same name instead. (Some languages work like this by design, but I've always found it a silly way to handle scope.)
|
||||
+This ambiguity causes a problem. When you try to give a nonlocal binding a new value, you will end up defining a local one with the same name instead. Some languages work like this by design, but I've always found it an awkward way to handle scope.
|
||||
|
||||
-Add a special form `set`, similar to `define`, which gives a variable a new value, updating the variable in an outer scope if it doesn't already exist in the inner scope. If the variable is not defined at all, throw a `ReferenceError` (which is another standard error type).
|
||||
+Add a special form `set`, similar to `define`, which gives a binding a new value, updating the binding in an outer scope if it doesn't already exist in the inner scope. If the binding is not defined at all, throw a `ReferenceError` (another standard error type).
|
||||
|
||||
-The technique of representing scopes as simple objects, which has made things convenient so far, will get in your way a little at this point. You might want to use the `Object.getPrototypeOf` function, which returns the prototype of an object. Also remember that scopes do not derive from `Object.prototype`, so if you want to call `hasOwnProperty` on them, you have to use this clumsy expression:
|
||||
+The technique of representing scopes as simple objects, which has made things convenient so far, will get in your way a little at this point. You might want to use the `Object.<wbr>getPrototypeOf` function, which returns the prototype of an object. Also remember that scopes do not derive from `Object.prototype`, so if you want to call `hasOwnProperty` on them, you have to use this clumsy expression:
|
||||
|
||||
```
|
||||
Object.prototype.hasOwnProperty.call(scope, name);
|
||||
```
|
||||
|
||||
-This fetches the `hasOwnProperty` method from the `Object` prototype and then calls it on a scope object.
|
||||
-
|
||||
```
|
||||
-specialForms["set"] = function(args, env) {
|
||||
+specialForms.set = (args, scope) => {
|
||||
// Your code here.
|
||||
};
|
||||
|
||||
-run("do(define(x, 4),",
|
||||
- " define(setx, fun(val, set(x, val))),",
|
||||
- " setx(50),",
|
||||
- " print(x))");
|
||||
+run(`
|
||||
+do(define(x, 4),
|
||||
+ define(setx, fun(val, set(x, val))),
|
||||
+ setx(50),
|
||||
+ print(x))
|
||||
+`);
|
||||
// → 50
|
||||
-run("set(quux, true)");
|
||||
+run(`set(quux, true)`);
|
||||
// → Some kind of ReferenceError
|
||||
```
|
||||
|
||||
-You will have to loop through one scope at a time, using `Object.getPrototypeOf` to go the next outer scope. For each scope, use `hasOwnProperty` to find out whether the variable, indicated by the `name` property of the first argument to `set`, exists in that scope. If it does, set it to the result of evaluating the second argument to `set` and then return that value.
|
||||
+You will have to loop through one scope at a time, using `Object.<wbr>getPrototypeOf` to go the next outer scope. For each scope, use `hasOwnProperty` to find out whether the binding, indicated by the `name` property of the first argument to `set`, exists in that scope. If it does, set it to the result of evaluating the second argument to `set` and then return that value.
|
||||
|
||||
-If the outermost scope is reached (`Object.getPrototypeOf` returns null) and we haven't found the variable yet, it doesn't exist, and an error should be thrown.
|
||||
+If the outermost scope is reached (`Object.<wbr>getPrototypeOf` returns null) and we haven't found the binding yet, it doesn't exist, and an error should be thrown.
|
199
diff-en/2ech12-3ech13.diff
Normal file
199
diff-en/2ech12-3ech13.diff
Normal file
@ -0,0 +1,199 @@
|
||||
diff --git a/2ech12.md b/3ech13.md
|
||||
index e9028ff..3565983 100644
|
||||
--- a/2ech12.md
|
||||
+++ b/3ech13.md
|
||||
@@ -1,30 +1,36 @@
|
||||
-# Chapter 12JavaScript and the Browser
|
||||
+# Chapter 13JavaScript and the Browser
|
||||
|
||||
-> The browser is a really hostile programming environment.
|
||||
+> The dream behind the Web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished.
|
||||
>
|
||||
-> <footer>Douglas Crockford, <cite>The JavaScript Programming Language (video lecture)</cite></footer>
|
||||
+> <footer>Tim Berners-Lee, <cite>The World Wide Web: A very short personal history</cite></footer>
|
||||
|
||||
-The next part of this book will talk about web browsers. Without web browsers, there would be no JavaScript. And even if there were, no one would ever have paid any attention to it.
|
||||
+The next chapters of this book will talk about web browsers. Without web browsers, there would be no JavaScript. Or even if there were, no one would ever have paid any attention to it.
|
||||
|
||||
-Web technology has, from the start, been decentralized, not just technically but also in the way it has evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought out ways, which then sometimes ended up being adopted by others and finally set down as a standard.
|
||||
+Web technology has, from the start, been decentralized, not just technically but also in the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought out ways, which then, sometimes, ended up being adopted by others—and finally set down as a standard.
|
||||
|
||||
-This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose collaboration (or, occasionally, open hostility). On the other hand, the haphazard way in which the Web was developed means that the resulting system is not exactly a shining example of internal consistency. In fact, some parts of it are downright messy and confusing.
|
||||
+This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose collaboration (or occasionally open hostility). On the other hand, the haphazard way in which the Web was developed means that the resulting system is not exactly a shining example of internal consistency. Some parts of it are downright confusing and poorly conceived.
|
||||
|
||||
## Networks and the Internet
|
||||
|
||||
Computer networks have been around since the 1950s. If you put cables between two or more computers and allow them to send data back and forth through these cables, you can do all kinds of wonderful things.
|
||||
|
||||
-If connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the _Internet_. It has lived up to its promise.
|
||||
+And if connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the _Internet_. It has lived up to its promise.
|
||||
|
||||
-A computer can use this network to spew bits at another computer. For any effective communication to arise out of this bit-spewing, the computers at both ends must know what the bits are supposed to represent. The meaning of any given sequence of bits depends entirely on the kind of thing that it is trying to express and on the encoding mechanism used.
|
||||
+A computer can use this network to shoot bits at another computer. For any effective communication to arise out of this bit-shooting, the computers on both ends must know what the bits are supposed to represent. The meaning of any given sequence of bits depends entirely on the kind of thing that it is trying to express and on the encoding mechanism used.
|
||||
|
||||
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.
|
||||
+For example, the HTTP protocol (_Hypertext Transfer Protocol_) is a protocol for retrieving named resources (chunks of information, such as web pages or pictures). It specifies that the side making the request should start with a line like this, naming the resource and the version of the protocol that it is trying to use.
|
||||
|
||||
-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.
|
||||
+```
|
||||
+GET /index.html HTTP/1.1
|
||||
+```
|
||||
+
|
||||
+There's a lot more rules about the way the requester can include more information in the request and the way the other side, which returns the resource, packages up its content. We'll look at HTTP in a little more detail in [Chapter 18](18_http.html).
|
||||
+
|
||||
+Most protocols are built on top of other protocols. HTTP 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. As we saw in [Chapter 11](11_async.html), ensuring those things is already a rather difficult 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.
|
||||
+The _Transmission Control Protocol_ (TCP) is a protocol that addresses 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.
|
||||
|
||||
@@ -36,34 +42,33 @@ Such a connection acts as a two-way pipe through which bits can flow—the machi
|
||||
|
||||
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.
|
||||
+To become part of the Web, all you need to do is connect a machine to the Internet, and have it listen on port 80 with the HTTP protocol, so that other computers can ask it for documents.
|
||||
|
||||
Each document on the Web is named by a _Uniform Resource Locator_ (URL), which looks something like this:
|
||||
|
||||
```
|
||||
- http://eloquentjavascript.net/12_browser.html
|
||||
+ http://eloquentjavascript.net/13_browser.html
|
||||
| | | |
|
||||
protocol server path
|
||||
```
|
||||
|
||||
The first part tells us that this URL uses the HTTP protocol (as opposed to, for example, encrypted HTTP, which would be _https://_). Then comes the part that identifies which server we are requesting the document from. Last is a path string that identifies the specific document (or _resource_) we are interested in.
|
||||
|
||||
-Each machine connected to the Internet gets a unique _IP address_, which looks something like `37.187.37.82`. You can use these directly as the server part of a URL. But lists of more or less random numbers are hard to remember and awkward to type, so you can instead register a _domain name_ to point toward a specific machine or set of machines. I registered _eloquentjavascript.net_ to point at the IP address of a machine I control and can thus use that domain name to serve web pages.
|
||||
+Machines connected to the Internet get an _IP address_, which is a number that can be used to send messages to that machine, and looks something like `149.210.142.219` or `2001:4860:4860::8888`. But lists of more or less random numbers are hard to remember and awkward to type, so you can instead register a _domain name_ for a specific address or set of addresses. I registered _eloquentjavascript.net_ to point at the IP address of a machine I control and can thus use that domain name to serve web pages.
|
||||
|
||||
-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_.
|
||||
-
|
||||
-We will take a closer look at the HTTP protocol in [Chapter 17](17_http.html#http).
|
||||
+If you type the URL we saw 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 will make a connection to the server at that address and ask for the resource _/13_browser.html_. If all goes well, the server sends back a document, which your browser then displays on your screen.
|
||||
|
||||
## HTML
|
||||
|
||||
HTML, which stands for _Hypertext Markup Language_, is the document format used for web pages. An HTML document contains text, as well as _tags_ that give structure to the text, describing things such as links, paragraphs, and headings.
|
||||
|
||||
-A simple HTML document looks like this:
|
||||
+A short HTML document might look like this:
|
||||
|
||||
```
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
+ <meta charset="utf-8">
|
||||
<title>My home page</title>
|
||||
</head>
|
||||
<body>
|
||||
@@ -75,19 +80,19 @@ A simple HTML document looks like this:
|
||||
</html>
|
||||
```
|
||||
|
||||
-The tags, wrapped in angle brackets (`<` and `>`), provide information about the structure of the document. The other text is just plain text.
|
||||
+The tags, wrapped in angle brackets (`<` and `>`, the symbols for _less than_ and _greater than_), provide information about the structure of the document. The other text is just plain text.
|
||||
|
||||
-The document starts with `<!doctype html>`, which tells the browser to interpret it as _modern_ HTML, as opposed to various dialects that were in use in the past.
|
||||
+The document starts with `<!doctype html>`, which tells the browser to interpret the page as _modern_ HTML, as opposed to various dialects that were in use in the past.
|
||||
|
||||
-HTML documents have a head and a body. The head contains information _about_ the document, and the body contains the document itself. In this case, we first declared that the title of this document is “My home page” and then gave a document containing a heading (`<h1>`, meaning “heading 1”—`<h2>` to `<h6>` produce more minor headings) and two paragraphs (`<p>`).
|
||||
+HTML documents have a head and a body. The head contains information _about_ the document, and the body contains the document itself. In this case, the head declares that the title of this document is “My home page” and that it uses the UTF-8 encoding, which is a way to encode Unicode text as binary data. The document's body contains a heading (`<h1>`, meaning “heading 1”—`<h2>` to `<h6>` produce more minor headings) and two paragraphs (`<p>`).
|
||||
|
||||
-Tags come in several forms. An element, such as the body, a paragraph, or a link, is started by an _opening tag_ like `<p>` and ended by a _closing tag_ like `</p>`. Some opening tags, such as the one for the link (`<a>`), contain extra information in the form of `name="value"` pairs. These are called _attributes_. In this case, the destination of the link is indicated with `href="http://eloquentjavascript.net"`, where `href` stands for “hypertext reference”.
|
||||
+Tags come in several forms. An element, such as the body, a paragraph, or a link, is started by an _opening tag_ like `<p>` and ended by a _closing tag_ like `</p>`. Some opening tags, such as the one for the link (`<a>`), contain extra information in the form of `name="value"` pairs. These are called _attributes_. In this case, the destination of the link is indicated with `href="http://<wbr>eloquentjavascript.<wbr>net"`, where `href` stands for “hypertext reference”.
|
||||
|
||||
-Some kinds of tags do not enclose anything and thus do not need to be closed. An example of this would be `<img src="http://example.com/image.jpg">`, which will display the image found at the given source URL.
|
||||
+Some kinds of tags do not enclose anything and thus do not need to be closed. The metadata tag `<meta charset="utf-8">` is an example of this.
|
||||
|
||||
To be able to include angle brackets in the text of a document, even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as `<` (“less than”), and a closing bracket is written as `>` (“greater than”). In HTML, an ampersand (`&`) character followed by a word and a semicolon (`;`) is called an _entity_, and will be replaced by the character it encodes.
|
||||
|
||||
-This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning, too, those need to be escaped as `&`. Inside an attribute, which is wrapped in double quotes, `"` can be used to insert an actual quote character.
|
||||
+This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning, too, those need to be escaped as `&`. Inside attribute values, which are wrapped in double quotes, `"` can be used to insert an actual quote character.
|
||||
|
||||
HTML is parsed in a remarkably error-tolerant way. When tags that should be there are missing, the browser reconstructs them. The way in which this is done has been standardized, and you can rely on all modern browsers to do it in the same way.
|
||||
|
||||
@@ -96,6 +101,7 @@ The following document will be treated just like the one shown previously:
|
||||
```
|
||||
<!doctype html>
|
||||
|
||||
+<meta charset=utf-8>
|
||||
<title>My home page</title>
|
||||
|
||||
<h1>My home page</h1>
|
||||
@@ -104,11 +110,11 @@ The following document will be treated just like the one shown previously:
|
||||
<a href=http://eloquentjavascript.net>here</a>.
|
||||
```
|
||||
|
||||
-The `<html>`, `<head>`, and `<body>` tags are gone completely. The browser knows that `<title>` belongs in a head, and that `<h1>` in a body. Furthermore, I am no longer explicitly closing the paragraphs since opening a new paragraph or ending the document will close them implicitly. The quotes around the link target are also gone.
|
||||
+The `<html>`, `<head>`, and `<body>` tags are gone completely. The browser knows the `<meta>` and `<title>` belong in the head, and that `<h1>` means the body has started. Furthermore, I am no longer explicitly closing the paragraphs since opening a new paragraph or ending the document will close them implicitly. The quotes around the attribute values are also gone.
|
||||
|
||||
This book will usually omit the `<html>`, `<head>`, and `<body>` tags from examples to keep them short and free of clutter. But I _will_ close tags and include quotes around attributes.
|
||||
|
||||
-I will also usually omit the doctype. This is not to be taken as an encouragement to omit doctype declarations. Browsers will often do ridiculous things when you forget them. You should consider doctypes implicitly present in examples, even when they are not actually shown in the text.
|
||||
+I will also usually omit the doctype and `charset` declaration. This is not to be taken as an encouragement to drop these from HTML documents. Browsers will often do ridiculous things when you forget them. You should consider the doctype and the `charset` metadata implicitly present in examples, even when they are not actually shown in the text.
|
||||
|
||||
## HTML and JavaScript
|
||||
|
||||
@@ -119,7 +125,7 @@ In the context of this book, the most important HTML tag is `<script>`. Th
|
||||
<script>alert("hello!");</script>
|
||||
```
|
||||
|
||||
-Such a script will run as soon as its `<script>` tag is encountered as the browser reads the HTML. The page shown earlier will pop up an `alert` dialog when opened.
|
||||
+Such a script will run as soon as its `<script>` tag is encountered while the browser reads the HTML. This page will pop up a dialog when opened—the `alert` function resembles `prompt`, in that it pops up a little window, but only shows a message without asking for input.
|
||||
|
||||
Including large programs directly in HTML documents is often impractical. The `<script>` tag can be given an `src` attribute in order to fetch a script file (a text file containing a JavaScript program) from a URL.
|
||||
|
||||
@@ -128,29 +134,31 @@ Including large programs directly in HTML documents is often impractical. The `&
|
||||
<script src="code/hello.js"></script>
|
||||
```
|
||||
|
||||
-The _code/hello.js_ file included here contains the same simple program, `alert("hello!")`. When an HTML page references other URLs as part of itself, for example an image file or a script—web browsers will retrieve them immediately and include them in the page.
|
||||
+The _code/hello.js_ file included here contains the same program—`alert("hello!")`. When an HTML page references other URLs as part of itself, for example an image file or a script—web browsers will retrieve them immediately and include them in the page.
|
||||
|
||||
A script tag must always be closed with `</script>`, even if it refers to a script file and doesn't contain any code. If you forget this, the rest of the page will be interpreted as part of the script.
|
||||
|
||||
-Some attributes can also contain a JavaScript program. The `<button>` tag shown next (which shows up as a button) has an `onclick` attribute, whose content will be run whenever the button is clicked.
|
||||
+You can load ES modules (see [Chapter 10](10_modules.html#es)) in the browser by giving your script tag a `type="module"` attribute. Such modules can depend on other modules by using URLs relative to themselves as module names in `import` declarations.
|
||||
+
|
||||
+Some attributes can also contain a JavaScript program. The `<button>` tag shown next (which shows up as a button) has an `onclick` attribute. The attribute's value will be run whenever the button is clicked.
|
||||
|
||||
```
|
||||
<button onclick="alert('Boom!');">DO NOT PRESS</button>
|
||||
```
|
||||
|
||||
-Note that I had to use single quotes for the string in the `onclick` attribute because double quotes are already used to quote the whole attribute. I could also have used `"`, but that'd make the program harder to read.
|
||||
+Note that I had to use single quotes for the string in the `onclick` attribute because double quotes are already used to quote the whole attribute. I could also have used `"`.
|
||||
|
||||
## 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.
|
||||
|
||||
-Yet the attraction of the Web is that you can surf it without necessarily trusting all the pages you visit. This is why browsers severely limit the things a JavaScript program may do: it can't look at the files on your computer or modify anything not related to the web page it was embedded in.
|
||||
+Yet the attraction of the Web is that you can browse it without necessarily trusting all the pages you visit. This is why browsers severely limit the things a JavaScript program may do: it can't look at the files on your computer or modify anything not related to the web page it was embedded in.
|
||||
|
||||
-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.
|
||||
+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, so that the programs playing in it can't actually get out.
|
||||
|
||||
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.
|
||||
|
||||
-Every now and then, someone comes up with a new way to circumvent the limitations of a browser and do something harmful, ranging from leaking minor private information to taking over the whole machine that the browser runs on. The browser developers respond by fixing the hole, and all is well again—that is, until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government or mafia.
|
||||
+Every now and then, someone comes up with a new way to circumvent the limitations of a browser and do something harmful, ranging from leaking minor private information to taking over the whole machine that the browser runs on. The browser developers respond by fixing the hole, and all is well again—until the next problem is discovered, and hopefully publicized, rather than secretly exploited by some government agency or mafia.
|
||||
|
||||
## Compatibility and the browser wars
|
||||
|
||||
@@ -158,8 +166,6 @@ In the early stages of the Web, a browser called Mosaic dominated the market. Af
|
||||
|
||||
This was the dark age of compatibility, often called the _browser wars_. Web developers were left with not one unified Web but two or three incompatible platforms. To make things worse, the browsers in use around 2003 were all full of bugs, and of course the bugs were different for each browser. Life was hard for people writing web pages.
|
||||
|
||||
-Mozilla Firefox, a not-for-profit offshoot of Netscape, challenged Internet Explorer's hegemony in the late 2000s. Because Microsoft was not particularly interested in staying competitive at the time, Firefox took quite a chunk of market share away from it. Around the same time, Google introduced its Chrome browser, and Apple's Safari browser gained popularity, leading to a situation where there were four major players, rather than one.
|
||||
-
|
||||
-The new players had a more serious attitude toward standards and better engineering practices, leading to less incompatibility and fewer bugs. Microsoft, seeing its market share crumble, came around and adopted these attitudes. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs.
|
||||
+Mozilla Firefox, a not-for-profit offshoot of Netscape, challenged Internet Explorer's position in the late 2000s. Because Microsoft was not particularly interested in staying competitive at the time, Firefox took a lot of market share away from it. Around the same time, Google introduced its Chrome browser, and Apple's Safari browser gained popularity, leading to a situation where there were four major players, rather than one.
|
||||
|
||||
-That is not to say that the situation is perfect just yet. Some of the people using the Web are, for reasons of inertia or corporate policy, stuck with very old browsers. Until those browsers die out entirely, writing websites that work for them will require a lot of arcane knowledge about their shortcomings and quirks. This book is not about those quirks. Rather, it aims to present the modern, sane style of web programming.
|
||||
+The new players had a more serious attitude toward standards and better engineering practices, giving us less incompatibility and fewer bugs. Microsoft, seeing its market share crumble, came around and adopted these attitudes in its Edge browser, which replaces Internet Explorer. If you are starting to learn web development today, consider yourself lucky. The latest versions of the major browsers behave quite uniformly and have relatively few bugs.
|
718
diff-en/2ech13-3ech14.diff
Normal file
718
diff-en/2ech13-3ech14.diff
Normal file
@ -0,0 +1,718 @@
|
||||
diff --git a/2ech13.md b/3ech14.md
|
||||
index 2f8209b..6081ee9 100644
|
||||
--- a/2ech13.md
|
||||
+++ b/3ech14.md
|
||||
@@ -1,12 +1,16 @@
|
||||
-# Chapter 13The Document Object Model
|
||||
+# Chapter 14The Document Object Model
|
||||
|
||||
-When you open a web page in your browser, the browser retrieves the page's HTML text and parses it, much like the way our parser from [Chapter 11](11_language.html#parsing) parsed programs. The browser builds up a model of the document's structure and then uses this model to draw the page on the screen.
|
||||
+> Too bad! Same old story! Once you've finished building your house you notice you've accidentally learned something that you really should have known—before you started.
|
||||
+>
|
||||
+> <footer>Friedrich Nietzsche, <cite>Beyond Good and Evil</cite></footer>
|
||||
|
||||
-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.
|
||||
+When you open a web page in your browser, the browser retrieves the page's HTML text and parses it, much like the way our parser from [Chapter 12](12_language.html#parsing) parsed programs. The browser builds up a model of the document's structure and 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. It is a data structure that you can read or modify. It acts as a _live_ data structure: when it's modified, the page on the screen is updated to reflect the changes.
|
||||
|
||||
## Document structure
|
||||
|
||||
-You can imagine an HTML document as a nested set of boxes. Tags such as `<body>` and `</body>` enclose other tags, which in turn contain other tags or text. Here's the example document from the [previous chapter](12_browser.html#browser):
|
||||
+You can imagine an HTML document as a nested set of boxes. Tags such as `<body>` and `</body>` enclose other tags, which in turn contain other tags or text. Here's the example document from the [previous chapter](13_browser.html):
|
||||
|
||||
```
|
||||
<!doctype html>
|
||||
@@ -25,62 +29,65 @@ You can imagine an HTML document as a nested set of boxes. Tags such as `<bod
|
||||
|
||||
This page has the following structure:
|
||||
|
||||
-
|
||||
+<figure></figure>
|
||||
|
||||
The data structure the browser uses to represent the document follows this shape. For each box, there is an object, which we can interact with to find out things such as what HTML tag it represents and which boxes and text it contains. This representation is called the _Document Object Model_, or DOM for short.
|
||||
|
||||
-The global variable `document` gives us access to these objects. Its `documentElement` property refers to the object representing the `<html>` tag. It also provides the properties `head` and `body`, which hold the objects for those elements.
|
||||
+The global binding `document` gives us access to these objects. Its `documentElement` property refers to the object representing the `<html>` tag. Since every HTML document has a head and a body, it also has `head` and `body` properties, pointing at those elements.
|
||||
|
||||
## Trees
|
||||
|
||||
-Think back to the syntax trees from [Chapter 11](11_language.html#parsing) for a moment. Their structures are strikingly similar to the structure of a browser's document. Each _node_ may refer to other nodes, _children_, which in turn may have their own children. This shape is typical of nested structures where elements can contain sub-elements that are similar to themselves.
|
||||
+Think back to the syntax trees from [Chapter 12](12_language.html#parsing) for a moment. Their structures are strikingly similar to the structure of a browser's document. Each _node_ may refer to other nodes, _children_, which in turn may have their own children. This shape is typical of nested structures where elements can contain sub-elements that are similar to themselves.
|
||||
|
||||
-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.
|
||||
+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.<wbr>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.
|
||||
+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 tree than in a flat array.
|
||||
|
||||
-A typical tree has different kinds of nodes. The syntax tree for [the Egg language](11_language.html#language) had variables, values, and application nodes. Application nodes always have children, whereas variables and values are _leaves_, or nodes without children.
|
||||
+A typical tree has different kinds of nodes. The syntax tree for [the Egg language](12_language.html) had identifiers, values, and application nodes. Application nodes may have children, whereas identifiers and values are _leaves_, nodes without children.
|
||||
|
||||
-The same goes for the DOM. Nodes for regular _elements_, which represent HTML tags, determine the structure of the document. These can have child nodes. An example of such a node is `document.body`. Some of these children can be leaf nodes, such as pieces of text or comments (comments are written between `<!--` and `-->` in HTML).
|
||||
+The same goes for the DOM. Nodes for _elements_, which represent HTML tags, determine the structure of the document. These can have child nodes. An example of such a node is `document.body`. Some of these children can be leaf nodes, such as pieces of text or comment nodes.
|
||||
|
||||
-Each DOM node object has a `nodeType` property, which contains a numeric code that identifies the type of node. Regular elements have the value 1, which is also defined as the constant property `document.ELEMENT_NODE`. Text nodes, representing a section of text in the document, have the value 3 (`document.TEXT_NODE`). Comments have the value 8 (`document.COMMENT_NODE`).
|
||||
+Each DOM node object has a `nodeType` property, which contains a code (number) that identifies the type of node. Elements have code 1, which is also defined as the constant property `document.<wbr>ELEMENT_NODE`. Text nodes, representing a section of text in the document, get code 3 (`document.<wbr>TEXT_NODE`). Comments have code 8 (`document.<wbr>COMMENT_NODE`).
|
||||
|
||||
-So another way to visualize our document tree is as follows:
|
||||
+Another way to visualize our document tree is as follows:
|
||||
|
||||
-
|
||||
+<figure></figure>
|
||||
|
||||
The leaves are text nodes, and the arrows indicate parent-child relationships between nodes.
|
||||
|
||||
## 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.
|
||||
+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 be a language-neutral interface that can be used in other systems as well—not just for HTML but also for XML, which is a generic data format with an HTML-like syntax.
|
||||
|
||||
This is unfortunate. Standards are often useful. But in this case, the advantage (cross-language consistency) isn't all that compelling. Having an interface that is properly integrated with the language you are using will save you more time than having a familiar interface across languages.
|
||||
|
||||
-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`.
|
||||
+As an example of this 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 `map`.
|
||||
|
||||
-Then there are issues that are simply poor design. For example, there is no way to create a new node and immediately add children or attributes to it. Instead, you have to first create it, then add the children one by one, and finally set the attributes one by one, using side effects. Code that interacts heavily with the DOM tends to get long, repetitive, and ugly.
|
||||
+Then there are issues that are simply poor design. For example, there is no way to create a new node and immediately add children or attributes to it. Instead, you have to first create it, then add the children and attributes one by one, using side effects. Code that interacts heavily with the DOM tends to get long, repetitive, and ugly.
|
||||
|
||||
-But these flaws aren't fatal. Since JavaScript allows us to create our own abstractions, it is easy to write some helper functions that allow you to express the operations you are performing in a clearer and shorter way. In fact, many libraries intended for browser programming come with such tools.
|
||||
+But these flaws aren't fatal. Since JavaScript allows us to create our own abstractions, it is possible to design improved ways to express the operations you are performing. Many libraries intended for browser programming come with such tools.
|
||||
|
||||
## Moving through the tree
|
||||
|
||||
DOM nodes contain a wealth of links to other nearby nodes. The following diagram illustrates these:
|
||||
|
||||
-
|
||||
+<figure></figure>
|
||||
|
||||
-Although the diagram shows only one link of each type, every node has a `parentNode` property that points to its containing node. Likewise, every element node (node type 1) has a `childNodes` property that points to an array-like object holding its children.
|
||||
+Although the diagram shows only one link of each type, every node has a `parentNode` property that points to the node it is part of. Likewise, every element node (node type 1) has a `childNodes` property that points to an array-like object holding its children.
|
||||
|
||||
In theory, you could move anywhere in the tree using just these parent and child links. But JavaScript also gives you access to a number of additional convenience links. The `firstChild` and `lastChild` properties point to the first and last child elements or have the value `null` for nodes without children. Similarly, `previousSibling` and `nextSibling` point to adjacent nodes, which are nodes with the same parent that appear immediately before or after the node itself. For a first child, `previousSibling` will be null, and for a last child, `nextSibling` will be null.
|
||||
|
||||
-When dealing with a nested data structure like this one, recursive functions are often useful. The following recursive function scans a document for text nodes containing a given string and returns `true` when it has found one:
|
||||
+There's also the `children` property, which is like `childNodes`, but which only contains element (type 1) children, not other types of child nodes. This can be useful when you aren't interested in text nodes.
|
||||
+
|
||||
+When dealing with a nested data structure like this one, recursive functions are often useful. The following function scans a document for text nodes containing a given string and returns `true` when it has found one:
|
||||
|
||||
```
|
||||
function talksAbout(node, string) {
|
||||
if (node.nodeType == document.ELEMENT_NODE) {
|
||||
- for (var i = 0; i < node.childNodes.length; i++) {
|
||||
- if (talksAbout(node.childNodes[i], string))
|
||||
+ for (let i = 0; i < node.childNodes.length; i++) {
|
||||
+ if (talksAbout(node.childNodes[i], string)) {
|
||||
return true;
|
||||
+ }
|
||||
}
|
||||
return false;
|
||||
} else if (node.nodeType == document.TEXT_NODE) {
|
||||
@@ -92,29 +99,31 @@ console.log(talksAbout(document.body, "book"));
|
||||
// → true
|
||||
```
|
||||
|
||||
-The `nodeValue` property of a text node refers to the string of text that it represents.
|
||||
+Because `childNodes` is not a real array, we can not loop over it with `for`/`of` and have to run over the index range using a regular `for` loop.
|
||||
+
|
||||
+The `nodeValue` property of a text node holds the string of text that it represents.
|
||||
|
||||
## Finding elements
|
||||
|
||||
-Navigating these links among parents, children, and siblings is often useful, as in the previous function, which runs through the whole document. But if we want to find a specific node in the document, reaching it by starting at `document.body` and blindly following a hard-coded path of links is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure we might want to change later. Another 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.
|
||||
+Navigating these links among parents, children, and siblings is often useful. But if we want to find a specific node in the document, reaching it by starting at `document.body` and following a fixed path of properties is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure you might want to change later. Another 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.
|
||||
|
||||
So if we want to get the `href` attribute of the link in that document, we don't want to say something like “Get the second child of the sixth child of the document body”. It'd be better if we could say “Get the first link in the document”. And we can.
|
||||
|
||||
```
|
||||
-var link = document.body.getElementsByTagName("a")[0];
|
||||
+let link = document.body.getElementsByTagName("a")[0];
|
||||
console.log(link.href);
|
||||
```
|
||||
|
||||
-All element nodes have a `getElementsByTagName` method, which collects all elements with the given tag name that are descendants (direct or indirect children) of the given node and returns them as an array-like object.
|
||||
+All element nodes have a `getElementsByTagName` method, which collects all elements with the given tag name that are descendants (direct or indirect children) of that node and returns them as an array-like object.
|
||||
|
||||
-To find a specific _single_ node, you can give it an `id` attribute and use `document.getElementById` instead.
|
||||
+To find a specific _single_ node, you can give it an `id` attribute and use `document.<wbr>getElementById` instead.
|
||||
|
||||
```
|
||||
<p>My ostrich Gertrude:</p>
|
||||
<p><img id="gertrude" src="img/ostrich.png"></p>
|
||||
|
||||
<script>
|
||||
- var ostrich = document.getElementById("gertrude");
|
||||
+ let ostrich = document.getElementById("gertrude");
|
||||
console.log(ostrich.src);
|
||||
</script>
|
||||
```
|
||||
@@ -123,7 +132,7 @@ A third, similar method is `getElementsByClassName`, which, like `getElementsByT
|
||||
|
||||
## Changing the document
|
||||
|
||||
-Almost everything about the DOM data structure can be changed. Element nodes have a number of methods that can be used to change their content. The `removeChild` method removes the given child node from the document. To add a child, we can use `appendChild`, which puts it at the end of the list of children, or `insertBefore`, which inserts the node given as the first argument before the node given as the second argument.
|
||||
+Almost everything about the DOM data structure can be changed. The shape of the document tree can be modified by changing parent-child relationships. Nodes have a `remove` method to remove them from their current parent node. To add a child node to an element node, we can use `appendChild`, which puts it at the end of the list of children, or `insertBefore`, which inserts the node given as the first argument before the node given as the second argument.
|
||||
|
||||
```
|
||||
<p>One</p>
|
||||
@@ -131,20 +140,20 @@ Almost everything about the DOM data structure can be changed. Element nodes hav
|
||||
<p>Three</p>
|
||||
|
||||
<script>
|
||||
- var paragraphs = document.body.getElementsByTagName("p");
|
||||
+ let 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).
|
||||
+A node can exist in the document in only one place. Thus, inserting paragraph _Three_ in front of paragraph _One_ will first remove it from the end of the document and then insert it at the front, resulting in _Three_/_One_/_Two_. All operations that insert a node somewhere will, as a side effect, cause it to be removed from its current position (if it has one).
|
||||
|
||||
The `replaceChild` method is used to replace a child node with another one. It takes as arguments two nodes: a new node and the node to be replaced. The replaced node must be a child of the element the method is called on. Note that both `replaceChild` and `insertBefore` expect the _new_ node as their first argument.
|
||||
|
||||
## Creating nodes
|
||||
|
||||
-In the following example, we want to write a script that replaces all images (`<img>` tags) in the document with the text held in their `alt` attributes, which specifies an alternative textual representation of the image.
|
||||
+Say we want to write a script that replaces all images (`<img>` tags) in the document with the text held in their `alt` attributes, which specifies an alternative textual representation of the image.
|
||||
|
||||
-This involves not only removing the images but adding a new text node to replace them. For this, we use the `document.createTextNode` method.
|
||||
+This involves not only removing the images but adding a new text node to replace them. Text nodes are created with the `document.<wbr>createTextNode` method.
|
||||
|
||||
```
|
||||
<p>The <img src="img/cat.png" alt="Cat"> in the
|
||||
@@ -154,11 +163,11 @@ This involves not only removing the images but adding a new text node to replace
|
||||
|
||||
<script>
|
||||
function replaceImages() {
|
||||
- var images = document.body.getElementsByTagName("img");
|
||||
- for (var i = images.length - 1; i >= 0; i--) {
|
||||
- var image = images[i];
|
||||
+ let images = document.body.getElementsByTagName("img");
|
||||
+ for (let i = images.length - 1; i >= 0; i--) {
|
||||
+ let image = images[i];
|
||||
if (image.alt) {
|
||||
- var text = document.createTextNode(image.alt);
|
||||
+ let text = document.createTextNode(image.alt);
|
||||
image.parentNode.replaceChild(text, image);
|
||||
}
|
||||
}
|
||||
@@ -166,23 +175,22 @@ This involves not only removing the images but adding a new text node to replace
|
||||
</script>
|
||||
```
|
||||
|
||||
-Given a string, `createTextNode` gives us a type 3 DOM node (a text node), which we can insert into the document to make it show up on the screen.
|
||||
+Given a string, `createTextNode` gives us a text node, which we can insert into the document to make it show up on the screen.
|
||||
|
||||
-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.
|
||||
+The loop that goes over the images starts at the end of the list. 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.
|
||||
+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 `Array.from`.
|
||||
|
||||
```
|
||||
-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
|
||||
+let arrayish = {0: "one", 1: "two", length: 2};
|
||||
+let array = Array.from(arrayish);
|
||||
+console.log(array.map(s => s.toUpperCase()));
|
||||
+// → ["ONE", "TWO"]
|
||||
```
|
||||
|
||||
-To create regular element nodes (type 1), you can use the `document.createElement` method. This method takes a tag name and returns a new empty node of the given type.
|
||||
+To create element nodes, you can use the `document.<wbr>createElement` method. This method takes a tag name and returns a new empty node of the given type.
|
||||
|
||||
-The following example defines a utility `elt`, which creates an element node and treats the rest of its arguments as children to that node. This function is then used to add a simple attribution to a quote.
|
||||
+The following example defines a utility `elt`, which creates an element node and treats the rest of its arguments as children to that node. This function is then used to add an attribution to a quote.
|
||||
|
||||
```
|
||||
<blockquote id="quote">
|
||||
@@ -192,13 +200,11 @@ The following example defines a utility `elt`, which creates an element node and
|
||||
</blockquote>
|
||||
|
||||
<script>
|
||||
- function elt(type) {
|
||||
- var node = document.createElement(type);
|
||||
- for (var i = 1; i < arguments.length; i++) {
|
||||
- var child = arguments[i];
|
||||
- if (typeof child == "string")
|
||||
- child = document.createTextNode(child);
|
||||
- node.appendChild(child);
|
||||
+ function elt(type, ...children) {
|
||||
+ let node = document.createElement(type);
|
||||
+ for (let child of children) {
|
||||
+ if (typeof child != "string") node.appendChild(child);
|
||||
+ else node.appendChild(document.createTextNode(child));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@@ -214,88 +220,37 @@ The following example defines a utility `elt`, which creates an element node and
|
||||
|
||||
## 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.
|
||||
+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 most 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.
|
||||
+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 have to use the `getAttribute` and `setAttribute` methods to work with them.
|
||||
|
||||
```
|
||||
<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.
|
||||
-
|
||||
-As a simple example, we'll write a “syntax highlighter” that looks for `<pre>` tags (“preformatted”, used for code and similar plaintext) with a `data-language` attribute and crudely tries to highlight the keywords for that language.
|
||||
-
|
||||
-```
|
||||
-function highlightCode(node, keywords) {
|
||||
- var text = node.textContent;
|
||||
- node.textContent = ""; // Clear the node
|
||||
-
|
||||
- var match, pos = 0;
|
||||
- while (match = keywords.exec(text)) {
|
||||
- var before = text.slice(pos, match.index);
|
||||
- node.appendChild(document.createTextNode(before));
|
||||
- var strong = document.createElement("strong");
|
||||
- strong.appendChild(document.createTextNode(match[0]));
|
||||
- node.appendChild(strong);
|
||||
- pos = keywords.lastIndex;
|
||||
- }
|
||||
- var after = text.slice(pos);
|
||||
- node.appendChild(document.createTextNode(after));
|
||||
-}
|
||||
-```
|
||||
-
|
||||
-The function `highlightCode` takes a `<pre>` node and a regular expression (with the “global” option turned on) that matches the keywords of the programming language that the element contains.
|
||||
-
|
||||
-The `textContent` property is used to get all the text in the node and is then set to an empty string, which has the effect of emptying the node. We loop over all matches of the keyword expression, appending the text _between_ them as regular text nodes, and the text matched (the keywords) as text nodes wrapped in `<strong>` (bold) elements.
|
||||
-
|
||||
-We can automatically highlight all programs on the page by looping over all the `<pre>` elements that have a `data-language` attribute and calling `highlightCode` on each one with the correct regular expression for the language.
|
||||
-
|
||||
-```
|
||||
-var languages = {
|
||||
- javascript: /\b(function|return|var)\b/g /* … etc */
|
||||
-};
|
||||
-
|
||||
-function highlightAllCode() {
|
||||
- var pres = document.body.getElementsByTagName("pre");
|
||||
- for (var i = 0; i < pres.length; i++) {
|
||||
- var pre = pres[i];
|
||||
- var lang = pre.getAttribute("data-language");
|
||||
- if (languages.hasOwnProperty(lang))
|
||||
- highlightCode(pre, languages[lang]);
|
||||
+ let paras = document.body.getElementsByTagName("p");
|
||||
+ for (let para of Array.from(paras)) {
|
||||
+ if (para.getAttribute("data-classified") == "secret") {
|
||||
+ para.remove();
|
||||
+ }
|
||||
}
|
||||
-}
|
||||
-```
|
||||
-
|
||||
-Here is an example:
|
||||
-
|
||||
+</script>
|
||||
```
|
||||
-<p>Here it is, the identity function:</p>
|
||||
-<pre data-language="javascript">
|
||||
-function id(x) { return x; }
|
||||
-</pre>
|
||||
|
||||
-<script>highlightAllCode();</script>
|
||||
-```
|
||||
+It is recommended to prefix 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.
|
||||
+There is a commonly used attribute, `class`, which is a keyword in the JavaScript language. For historical reasons—some old JavaScript implementations could not handle property names that matched keywords—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
|
||||
|
||||
-You might have noticed that different types of elements are laid out differently. Some, such as paragraphs (`<p>`) or headings (`<h1>`), take up the whole width of the document and are rendered on separate lines. These are called _block_ elements. Others, such as links (`<a>`) or the `<strong>` element used in the previous example, are rendered on the same line with their surrounding text. Such elements are called _inline_ elements.
|
||||
+You may have noticed that different types of elements are laid out differently. Some, such as paragraphs (`<p>`) or headings (`<h1>`), take up the whole width of the document and are rendered on separate lines. These are called _block_ elements. Others, such as links (`<a>`) or the `<strong>` element, are rendered on the same line with their surrounding text. Such elements are called _inline_ elements.
|
||||
|
||||
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 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. It traditionally corresponds to the smallest dot that the screen can draw, but on modern displays, which can draw _very_ small dots, that may no longer be the case, and a browser pixel may span multiple display dots.
|
||||
+
|
||||
+Similarly, `clientWidth` and `clientHeight` give you the size of the space _inside_ the element, ignoring border width.
|
||||
|
||||
```
|
||||
<p style="border: 3px solid red">
|
||||
@@ -303,17 +258,17 @@ The size and position of an element can be accessed from JavaScript. The `offset
|
||||
</p>
|
||||
|
||||
<script>
|
||||
- var para = document.body.getElementsByTagName("p")[0];
|
||||
+ let para = document.body.getElementsByTagName("p")[0];
|
||||
console.log("clientHeight:", para.clientHeight);
|
||||
console.log("offsetHeight:", para.offsetHeight);
|
||||
</script>
|
||||
```
|
||||
|
||||
-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.
|
||||
+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, which you can find in the `pageXOffset` and `pageYOffset` bindings.
|
||||
|
||||
-Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time it is changed but rather wait as long as they can. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout in order to display the changed document on the screen. When a program _asks_ for the position or size of something by reading properties such as `offsetHeight` or calling `getBoundingClientRect`, providing correct information also requires computing a layout.
|
||||
+Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time you change it, but wait as long as they can. When a JavaScript program that changed the document finishes running, the browser will have to compute a new layout in order to draw the changed document to the screen. When a program _asks_ for the position or size of something by reading properties such as `offsetHeight` or calling `getBoundingClientRect`, providing correct information also requires computing a layout.
|
||||
|
||||
-A program that repeatedly alternates between reading DOM layout information and changing the DOM forces a lot of layouts to happen and will consequently run really slowly. The following code shows an example of this. It contains two different programs that build up a line of _X_ characters 2,000 pixels wide and measures the time each one takes.
|
||||
+A program that repeatedly alternates between reading DOM layout information and changing the DOM forces a lot of layout computations to happen and will consequently run very slowly. The following code is an example of this. It contains two different programs that build up a line of _X_ characters 2,000 pixels wide and measures the time each one takes.
|
||||
|
||||
```
|
||||
<p><span id="one"></span></p>
|
||||
@@ -321,24 +276,24 @@ A program that repeatedly alternates between reading DOM layout information and
|
||||
|
||||
<script>
|
||||
function time(name, action) {
|
||||
- var start = Date.now(); // Current time in milliseconds
|
||||
+ let start = Date.now(); // Current time in milliseconds
|
||||
action();
|
||||
console.log(name, "took", Date.now() - start, "ms");
|
||||
}
|
||||
|
||||
- time("naive", function() {
|
||||
- var target = document.getElementById("one");
|
||||
- while (target.offsetWidth < 2000)
|
||||
+ time("naive", () => {
|
||||
+ let target = document.getElementById("one");
|
||||
+ while (target.offsetWidth < 2000) {
|
||||
target.appendChild(document.createTextNode("X"));
|
||||
+ }
|
||||
});
|
||||
// → naive took 32 ms
|
||||
|
||||
time("clever", function() {
|
||||
- var target = document.getElementById("two");
|
||||
+ let target = document.getElementById("two");
|
||||
target.appendChild(document.createTextNode("XXXXX"));
|
||||
- var total = Math.ceil(2000 / (target.offsetWidth / 5));
|
||||
- for (var i = 5; i < total; i++)
|
||||
- target.appendChild(document.createTextNode("X"));
|
||||
+ let total = Math.ceil(2000 / (target.offsetWidth / 5));
|
||||
+ target.firstChild.nodeValue = "X".repeat(total);
|
||||
});
|
||||
// → clever took 1 ms
|
||||
</script>
|
||||
@@ -346,9 +301,9 @@ A program that repeatedly alternates between reading DOM layout information and
|
||||
|
||||
## Styling
|
||||
|
||||
-We have seen that different HTML elements display different behavior. Some are displayed as blocks, others inline. Some add styling, such as `<strong>` making its content bold and `<a>` making it blue and underlining it.
|
||||
+We have seen that different HTML elements are drawn differently. Some are displayed as blocks, others inline. Some add styling—`<strong>` makes its content bold and `<a>` makes it blue and underlines it.
|
||||
|
||||
-The way an `<img>` tag shows an image or an `<a>` tag causes a link to be followed when it is clicked is strongly tied to the element type. But the default styling associated with an element, such as the text color or underline, can be changed by us. Here is an example using the `style` property:
|
||||
+The way an `<img>` tag shows an image or an `<a>` tag causes a link to be followed when it is clicked is strongly tied to the element type. But the default styling associated with an element, such as the text color or underline, can be changed by us. Here is an example that uses the `style` property:
|
||||
|
||||
```
|
||||
<p><a href=".">Normal link</a></p>
|
||||
@@ -357,7 +312,7 @@ The way an `<img>` tag shows an image or an `<a>` tag causes a link
|
||||
|
||||
A style attribute may contain one or more _declarations_, which are a property (such as `color`) followed by a colon and a value (such as `green`). When there is more than one declaration, they must be separated by semicolons, as in `"color: red; border: none"`.
|
||||
|
||||
-There are a lot of aspects that can be influenced by styling. For example, the `display` property controls whether an element is displayed as a block or an inline element.
|
||||
+There are a lot of aspects of the document that can be influenced by styling. For example, the `display` property controls whether an element is displayed as a block or an inline element.
|
||||
|
||||
```
|
||||
This text is displayed <strong>inline</strong>,
|
||||
@@ -365,23 +320,23 @@ This text is displayed <strong>inline</strong>,
|
||||
<strong style="display: none">not at all</strong>.
|
||||
```
|
||||
|
||||
-The `block` tag will end up on its own line since block elements are not displayed inline with the text around them. The last tag is not displayed at all—`display: none` prevents an element from showing up on the screen. This is a way to hide elements. It is often preferable to removing them from the document entirely because it makes it easy to reveal them again at a later time.
|
||||
+The `block` tag will end up on its own line since block elements are not displayed inline with the text around them. The last tag is not displayed at all—`display: none` prevents an element from showing up on the screen. This is a way to hide elements. It is often preferable to removing them from the document entirely because it makes it easy to reveal them again later.
|
||||
|
||||
-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. The values of these properties are strings, which we can write to in order to change a particular aspect of the element's style.
|
||||
+JavaScript code can directly manipulate the style of an element through the element's `style` property. This property holds an object that has properties for all possible style properties. The values of these properties are strings, which we can write to in order to change a particular aspect of the element's style.
|
||||
|
||||
```
|
||||
<p id="para" style="color: purple">
|
||||
- Pretty text
|
||||
+ Nice text
|
||||
</p>
|
||||
|
||||
<script>
|
||||
- var para = document.getElementById("para");
|
||||
+ let para = document.getElementById("para");
|
||||
console.log(para.style.color);
|
||||
para.style.color = "magenta";
|
||||
</script>
|
||||
```
|
||||
|
||||
-Some style property names contain dashes, such as `font-family`. Because such property names are awkward to work with in JavaScript (you'd have to say `style["font-family"]`), the property names in the `style` object for such properties have their dashes removed and the letters that follow them capitalized (`style.fontFamily`).
|
||||
+Some style property names contain dashes, such as `font-family`. Because such property names are awkward to work with in JavaScript (you'd have to say `style["font-family"]`), the property names in the `style` object for such properties have their dashes removed and the letters after them capitalized (`style.fontFamily`).
|
||||
|
||||
## Cascading styles
|
||||
|
||||
@@ -397,11 +352,11 @@ The styling system for HTML is called CSS for _Cascading Style Sheets_. A _style
|
||||
<p>Now <strong>strong text</strong> is italic and gray.</p>
|
||||
```
|
||||
|
||||
-The _cascading_ in the name refers to the fact that multiple such rules are combined to produce the final style for an element. In the previous example, the default styling for `<strong>` tags, which gives them `font-weight: bold`, is overlaid by the rule in the `<style>` tag, which adds `font-style` and `color`.
|
||||
+The _cascading_ in the name refers to the fact that multiple such rules are combined to produce the final style for an element. In the example, the default styling for `<strong>` tags, which gives them `font-weight: bold`, is overlaid by the rule in the `<style>` tag, which adds `font-style` and `color`.
|
||||
|
||||
-When multiple rules define a value for the same property, the most recently read rule gets a higher precedence and wins. So if the rule in the `<style>` tag included `font-weight: normal`, conflicting with the default `font-weight` rule, the text would be normal, _not_ bold. Styles in a `style` attribute applied directly to the node have the highest precedence and always win.
|
||||
+When multiple rules define a value for the same property, the most recently read rule gets a higher precedence and wins. So if the rule in the `<style>` tag included `font-weight: normal`, contradicting the default `font-weight` rule, the text would be normal, _not_ bold. Styles in a `style` attribute applied directly to the node have the highest precedence and always win.
|
||||
|
||||
-It is possible to target things other than tag names in CSS rules. A rule for `.abc` applies to all elements with `"abc"` in their class attributes. A rule for `#xyz` applies to the element with an `id` attribute of `"xyz"` (which should be unique within the document).
|
||||
+It is possible to target things other than tag names in CSS rules. A rule for `.abc` applies to all elements with `"abc"` in their `class` attribute. A rule for `#xyz` applies to the element with an `id` attribute of `"xyz"` (which should be unique within the document).
|
||||
|
||||
```
|
||||
.subtle {
|
||||
@@ -412,19 +367,19 @@ It is possible to target things other than tag names in CSS rules. A rule for `.
|
||||
background: blue;
|
||||
color: white;
|
||||
}
|
||||
-/* p elements, with classes a and b, and id main */
|
||||
-p.a.b#main {
|
||||
+/* p elements with id main and with classes a and b */
|
||||
+p#main.a.b {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
```
|
||||
|
||||
-The precedence rule favoring the most recently defined rule holds true only when the rules have the same _specificity_. A rule's specificity is a measure of how precisely it describes matching elements, determined by the number and kind (tag, class, or ID) of element aspects it requires. For example, a rule that targets `p.a` is more specific than rules that target `p` or just `.a`, and would thus take precedence over them.
|
||||
+The precedence rule favoring the most recently defined rule applies only when the rules have the same _specificity_. A rule's specificity is a measure of how precisely it describes matching elements, determined by the number and kind (tag, class, or ID) of element aspects it requires. For example, a rule that targets `p.a` is more specific than rules that target `p` or just `.a`, and would thus take precedence over them.
|
||||
|
||||
The notation `p > a {…}` applies the given styles to all `<a>` tags that are direct children of `<p>` tags. Similarly, `p a {…}` applies to all `<a>` tags inside `<p>` tags, whether they are direct or indirect children.
|
||||
|
||||
## Query selectors
|
||||
|
||||
-We won't be using style sheets all that much in this book. Although understanding them is crucial to programming in the browser, properly explaining all the properties they support and the interaction among those properties would take two or three books.
|
||||
+We won't be using style sheets all that much in this book. Understanding them is helpful when programming in the browser, but they are complicated enough to warrant a separate book.
|
||||
|
||||
The main reason I introduced _selector_ syntax—the notation used in style sheets to determine which elements a set of styles apply to—is that we can use this same mini-language as an effective way to find DOM elements.
|
||||
|
||||
@@ -453,73 +408,75 @@ The `querySelectorAll` method, which is defined both on the `document` object an
|
||||
</script>
|
||||
```
|
||||
|
||||
-Unlike methods such as `getElementsByTagName`, the object returned by `querySelectorAll` is _not_ live. It won't change when you change the document.
|
||||
+Unlike methods such as `getElementsByTagName`, the object returned by `querySelectorAll` is _not_ live. It won't change when you change the document. It is still not a real array, though, so you still need to call `Array.from` if you want to treat it like one.
|
||||
|
||||
-The `querySelector` method (without the `All` part) works in a similar way. This one is useful if you want a specific, single element. It will return only the first matching element or null if no elements match.
|
||||
+The `querySelector` method (without the `All` part) works in a similar way. This one is useful if you want a specific, single element. It will return only the first matching element or null when no element matches.
|
||||
|
||||
## 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.
|
||||
+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 that normal place. When `position` is set to `absolute`, the element is removed from the normal document flow—that is, it no longer takes up space and may overlap with other elements. Also, its `top` and `left` properties can be used to absolutely position it relative to the top-left corner of the nearest enclosing element whose `position` property isn't `static`, or relative to the document if no such enclosing element exists.
|
||||
|
||||
-We can use this to create an animation. The following document displays a picture of a cat that floats around in an ellipse:
|
||||
+We can use this to create an animation. The following document displays a picture of a cat that moves around in an ellipse:
|
||||
|
||||
```
|
||||
<p style="text-align: center">
|
||||
<img src="img/cat.png" style="position: relative">
|
||||
</p>
|
||||
<script>
|
||||
- var cat = document.querySelector("img");
|
||||
- var angle = 0, lastTime = null;
|
||||
- function animate(time) {
|
||||
- if (lastTime != null)
|
||||
+ let cat = document.querySelector("img");
|
||||
+ let angle = Math.PI / 2;
|
||||
+ function animate(time, lastTime) {
|
||||
+ if (lastTime != null) {
|
||||
angle += (time - lastTime) * 0.001;
|
||||
- lastTime = time;
|
||||
+ }
|
||||
cat.style.top = (Math.sin(angle) * 20) + "px";
|
||||
cat.style.left = (Math.cos(angle) * 200) + "px";
|
||||
- requestAnimationFrame(animate);
|
||||
+ requestAnimationFrame(newTime => animate(newTime, time));
|
||||
}
|
||||
requestAnimationFrame(animate);
|
||||
</script>
|
||||
```
|
||||
|
||||
-The picture is centered on the page and given a `position` of `relative`. We'll repeatedly update that picture's `top` and `left` styles in order to move it.
|
||||
+Our picture is centered on the page and given a `position` of `relative`. We'll repeatedly update that picture's `top` and `left` styles in order to move it.
|
||||
|
||||
The script uses `requestAnimationFrame` to schedule the `animate` function to run whenever the browser is ready to repaint the screen. The `animate` function itself again calls `requestAnimationFrame` to schedule the next update. When the browser window (or tab) is active, this will cause updates to happen at a rate of about 60 per second, which tends to produce a good-looking animation.
|
||||
|
||||
If we just updated the DOM in a loop, the page would freeze and nothing would show up on the screen. Browsers do not update their display while a JavaScript program is running, nor do they allow any interaction with the page. This is why we need `requestAnimationFrame`—it lets the browser know that we are done for now, and it can go ahead and do the things that browsers do, such as updating the screen and responding to user actions.
|
||||
|
||||
-Our animation function is passed the current time as an argument, which it compares to the time it saw before (the `lastTime` variable) to ensure the motion of the cat per millisecond is stable, and the animation moves smoothly. If it just moved a fixed amount per step, the motion would stutter if, for example, another heavy task running on the same computer were to prevent the function from running for a fraction of a second.
|
||||
+The animation function is passed the current time as an argument. To ensure the motion of the cat per millisecond is stable, it bases the speed at which the angle changes on the difference between the current time and the last time the function ran. If it just moved the angle by a fixed amount per step, the motion would stutter if, for example, another heavy task running on the same computer were to prevent the function from running for a fraction of a second.
|
||||
+
|
||||
+Moving in circles is done using the trigonometry functions `Math.cos` and `Math.sin`. For those of you who aren't familiar with these, I'll briefly introduce them since we will occasionally use them in this book.
|
||||
|
||||
-Moving in circles is done using the trigonometry functions `Math.cos` and `Math.sin`. For those of you who aren't familiar with these, I'll briefly introduce them since we will occasionally need them in this book.
|
||||
+`Math.cos` and `Math.sin` are useful for finding points that lie on a circle around point (0,0) with a radius of one. Both functions interpret their argument as the position on this circle, with zero denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. `Math.cos` tells you the x-coordinate of the point that corresponds to the given position, while `Math.sin` yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that _a_+2π refers to the same angle as _a_.
|
||||
|
||||
-`Math.cos` and `Math.sin` are useful for finding points that lie on a circle around point (0,0) with a radius of one unit. Both functions interpret their argument as the position on this circle, with zero denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. `Math.cos` tells you the x-coordinate of the point that corresponds to the given position around the circle, while `Math.sin` yields the y-coordinate. Positions (or angles) greater than 2π or less than 0 are valid—the rotation repeats so that _a_+2π refers to the same angle as _a_.
|
||||
+This unit for measuring angles is called radians—a full circle is 2π radians, similar to how it is 360 degrees when measuring in degrees. The constant π is available as `Math.PI` in JavaScript.
|
||||
|
||||
-
|
||||
+<figure></figure>
|
||||
|
||||
-The cat animation code keeps a counter, `angle`, for the current angle of the animation and increments it in proportion to the elapsed time every time the `animate` function is called. It can then use this angle to compute the current position of the image element. The `top` style is computed with `Math.sin` and multiplied by 20, which is the vertical radius of our circle. The `left` style is based on `Math.cos` and multiplied by 200 so that the circle is much wider than it is high, resulting in an elliptic motion.
|
||||
+The cat animation code keeps a counter, `angle`, for the current angle of the animation and increments it every time the `animate` function is called. It can then use this angle to compute the current position of the image element. The `top` style is computed with `Math.sin` and multiplied by 20, which is the vertical radius of our ellipse. The `left` style is based on `Math.cos` and multiplied by 200 so that the ellipse is much wider than it is high.
|
||||
|
||||
Note that styles usually need _units_. In this case, we have to append `"px"` to the number to tell the browser we are counting in pixels (as opposed to centimeters, “ems”, or other units). This is easy to forget. Using numbers without units will result in your style being ignored—unless the number is 0, which always means the same thing, regardless of its unit.
|
||||
|
||||
## 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.
|
||||
+JavaScript programs may inspect and interfere with the document that the 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.
|
||||
+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 code can manipulate an element's style directly through its `style` property.
|
||||
|
||||
## Exercises
|
||||
|
||||
### Build a table
|
||||
|
||||
-We built plaintext tables in [Chapter 6](06_object.html#tables). HTML makes laying out tables quite a bit easier. An HTML table is built with the following tag structure:
|
||||
+An HTML table is built with the following tag structure:
|
||||
|
||||
```
|
||||
<table>
|
||||
<tr>
|
||||
<th>name</th>
|
||||
<th>height</th>
|
||||
- <th>country</th>
|
||||
+ <th>place</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kilimanjaro</td>
|
||||
@@ -531,42 +488,45 @@ We built plaintext tables in [Chapter 6](06_object.html#tables). HTML makes layi
|
||||
|
||||
For each _row_, the `<table>` tag contains a `<tr>` tag. Inside of these `<tr>` tags, we can put cell elements: either heading cells (`<th>`) or regular cells (`<td>`).
|
||||
|
||||
-The same source data that was used in [Chapter 6](06_object.html#mountains) is again available in the `MOUNTAINS` variable in the sandbox. It can also be [downloaded](http://eloquentjavascript.net/2nd_edition/code/mountains.js) from the website.
|
||||
+Given a data set of mountains, an array of objects with `name`, `height`, and `place` properties, generate the DOM structure for a table that enumerates the objects. It should have one column per key and one row per object, plus a header row with `<th>` elements at the top, listing the column names.
|
||||
|
||||
-Write a function `buildTable` that, given an array of objects that all have the same set of properties, builds up a DOM structure representing a table. The table should have a header row with the property names wrapped in `<th>` elements and should have one subsequent row per object in the array, with its property values in `<td>` elements.
|
||||
+Write this so that the columns are automatically derived from the objects, by taking the property names of the first object in the data.
|
||||
|
||||
-The `Object.keys` function, which returns an array containing the property names that an object has, will probably be helpful here.
|
||||
+Add the resulting table to the element with an `id` attribute of `"mountains"`, so that it becomes visible in the document.
|
||||
|
||||
-Once you have the basics working, right-align cells containing numbers by setting their `style.textAlign` property to `"right"`.
|
||||
+Once you have this working, right-align cells that contain number values by setting their `style.textAlign` property to `"right"`.
|
||||
|
||||
```
|
||||
-<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>
|
||||
+<h1>Mountains</h1>
|
||||
|
||||
-<script>
|
||||
- function buildTable(data) {
|
||||
- // Your code here.
|
||||
- }
|
||||
+<div id="mountains"></div>
|
||||
|
||||
- document.body.appendChild(buildTable(MOUNTAINS));
|
||||
+<script>
|
||||
+ const MOUNTAINS = [
|
||||
+ {name: "Kilimanjaro", height: 5895, place: "Tanzania"},
|
||||
+ {name: "Everest", height: 8848, place: "Nepal"},
|
||||
+ {name: "Mount Fuji", height: 3776, place: "Japan"},
|
||||
+ {name: "Vaalserberg", height: 323, place: "Netherlands"},
|
||||
+ {name: "Denali", height: 6168, place: "United States"},
|
||||
+ {name: "Popocatepetl", height: 5465, place: "Mexico"},
|
||||
+ {name: "Mont Blanc", height: 4808, place: "Italy/France"}
|
||||
+ ];
|
||||
+
|
||||
+ // Your code here
|
||||
</script>
|
||||
```
|
||||
|
||||
-Use `document.createElement` to create new element nodes, `document.createTextNode` to create text nodes, and the `appendChild` method to put nodes into other nodes.
|
||||
+You can use `document.<wbr>createElement` to create new element nodes, `document.<wbr>createTextNode` to create text nodes, and the `appendChild` method to put nodes into other nodes.
|
||||
|
||||
-You should loop over the key names once to fill in the top row and then again for each object in the array to construct the data rows.
|
||||
+You'll want to loop over the key names once to fill in the top row and then again for each object in the array to construct the data rows. To get an array of key names from the first object, `Object.keys` will be useful.
|
||||
|
||||
-Don't forget to return the enclosing `<table>` element at the end of the function.
|
||||
+To add the table to the correct parent node, you can use `document.<wbr>getElementById` or `document.<wbr>querySelector` to find the node with the proper `id` attribute.
|
||||
|
||||
### Elements by tag name
|
||||
|
||||
-The `getElementsByTagName` method returns all child elements with a given tag name. Implement your own version of it as a regular nonmethod function that takes a node and a string (the tag name) as arguments and returns an array containing all descendant element nodes with the given tag name.
|
||||
+The `document.<wbr>getElementsByTagName` method returns all child elements with a given tag name. Implement your own version of this as a function that takes a node and a string (the tag name) as arguments and returns an array containing all descendant element nodes with the given tag name.
|
||||
|
||||
-To find the tag name of an element, use its `tagName` property. But note that this will return the tag name in all uppercase. Use the `toLowerCase` or `toUpperCase` string method to compensate for this.
|
||||
+To find the tag name of an element, use its `nodeName` property. But note that this will return the tag name in all uppercase. Use the `toLowerCase` or `toUpperCase` string methods to compensate for this.
|
||||
|
||||
```
|
||||
<h1>Heading with a <span>span</span> element.</h1>
|
||||
@@ -582,33 +542,49 @@ To find the tag name of an element, use its `tagName` property. But note that th
|
||||
// → 1
|
||||
console.log(byTagName(document.body, "span").length);
|
||||
// → 3
|
||||
- var para = document.querySelector("p");
|
||||
+ let para = document.querySelector("p");
|
||||
console.log(byTagName(para, "span").length);
|
||||
// → 2
|
||||
</script>
|
||||
```
|
||||
|
||||
-The solution is most easily expressed with a recursive function, similar to the [`talksAbout` function](13_dom.html#talksAbout) defined earlier in this chapter.
|
||||
+The solution is most easily expressed with a recursive function, similar to the [`talksAbout` function](14_dom.html#talksAbout) defined earlier in this chapter.
|
||||
|
||||
-You could call `byTagname` itself recursively, concatenating the resulting arrays to produce the output. For a more efficient approach, define an inner function that calls itself recursively and that has access to an array variable defined in the outer function to which it can add the matching elements it finds. Don't forget to call the inner function once from the outer function.
|
||||
+You could call `byTagname` itself recursively, concatenating the resulting arrays to produce the output. Or you can create an inner function that calls itself recursively and that has access to an array binding defined in the outer function, to which it can add the matching elements it finds. Don't forget to call the inner function once from the outer function to start the process.
|
||||
|
||||
-The recursive function must check the node type. Here we are interested only in node type 1 (`document.ELEMENT_NODE`). For such nodes, we must loop over their children and, for each child, see whether the child matches the query while also doing a recursive call on it to inspect its own children.
|
||||
+The recursive function must check the node type. Here we are interested only in node type 1 (`document.<wbr>ELEMENT_NODE`). For such nodes, we must loop over their children and, for each child, see whether the child matches the query while also doing a recursive call on it to inspect its own children.
|
||||
|
||||
### The cat's hat
|
||||
|
||||
-Extend the cat animation defined [earlier](13_dom.html#animation) so that both the cat and his hat (`<img src="img/hat.png">`) orbit at opposite sides of the ellipse.
|
||||
+Extend the cat animation defined [earlier](14_dom.html#animation) so that both the cat and his hat (`<img src="img/<wbr>hat.<wbr>png">`) orbit at opposite sides of the ellipse.
|
||||
|
||||
Or make the hat circle around the cat. Or alter the animation in some other interesting way.
|
||||
|
||||
-To make positioning multiple objects easier, it is probably a good idea to switch to absolute positioning. This means that `top` and `left` are counted relative to the top left of the document. To avoid using negative coordinates, you can simply add a fixed number of pixels to the position values.
|
||||
+To make positioning multiple objects easier, it is probably a good idea to switch to absolute positioning. This means that `top` and `left` are counted relative to the top left of the document. To avoid using negative coordinates, which would cause the image to move outside of the visible page, you can add a fixed number of pixels to the position values.
|
||||
|
||||
```
|
||||
+<style>body { min-height: 200px }</style>
|
||||
<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");
|
||||
- // Your code here.
|
||||
+ let cat = document.querySelector("#cat");
|
||||
+ let hat = document.querySelector("#hat");
|
||||
+
|
||||
+ let angle = 0;
|
||||
+ let lastTime = null;
|
||||
+ function animate(time) {
|
||||
+ if (lastTime != null) angle += (time - lastTime) * 0.001;
|
||||
+ lastTime = time;
|
||||
+ cat.style.top = (Math.sin(angle) * 40 + 40) + "px";
|
||||
+ cat.style.left = (Math.cos(angle) * 200 + 230) + "px";
|
||||
+
|
||||
+ // Your extensions here.
|
||||
+
|
||||
+ requestAnimationFrame(animate);
|
||||
+ }
|
||||
+ requestAnimationFrame(animate);
|
||||
</script>
|
||||
```
|
||||
+
|
||||
+`Math.cos` and `Math.sin` measure angles in radians, where a full circle is 2π. For a given angle, you can get the opposite angle by adding half of this, one time `Math.PI`. This can be useful for putting the hat on the opposite side of the orbit.
|
789
diff-en/2ech14-3ech15.diff
Normal file
789
diff-en/2ech14-3ech15.diff
Normal file
@ -0,0 +1,789 @@
|
||||
diff --git a/2ech14.md b/3ech15.md
|
||||
index 311ca63..8bb4de0 100644
|
||||
--- a/2ech14.md
|
||||
+++ b/3ech15.md
|
||||
@@ -1,57 +1,59 @@
|
||||
-# Chapter 14Handling Events
|
||||
+# Chapter 15Handling Events
|
||||
|
||||
> You have power over your mind—not outside events. Realize this, and you will find strength.
|
||||
>
|
||||
> <footer>Marcus Aurelius, <cite>Meditations</cite></footer>
|
||||
|
||||
-Some programs work with direct user input, such as mouse and keyboard interaction. The timing and order of such input can't be predicted in advance. This requires a different approach to control flow than the one we have used so far.
|
||||
+Some programs work with direct user input, such as mouse and keyboard actions. That kind of input isn't available as neatly organized data structure—it comes in piece by piece, in real time, and the program is expected to respond to it as it happens.
|
||||
|
||||
## Event handlers
|
||||
|
||||
Imagine an interface where the only way to find out whether a key on the keyboard is being pressed is to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key's state so that you'd catch it before it's released again. It would be dangerous to perform other time-intensive computations since you might miss a keypress.
|
||||
|
||||
-That is how such input was handled on primitive machines. A step up would be for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there.
|
||||
+Some primitive machines do handle input like that. A step up from this would be for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there.
|
||||
|
||||
-Of course, it has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called _polling_. Most programmers avoid it whenever possible.
|
||||
+Of course, it has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called _polling_. Most programmers prefer to avoid it.
|
||||
|
||||
-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.
|
||||
+A better mechanism is for the system to actively notify our code when an event occurs. Browsers do this by allowing us to register functions as _handlers_ for specific events.
|
||||
|
||||
```
|
||||
<p>Click this document to activate the handler.</p>
|
||||
<script>
|
||||
- addEventListener("click", function() {
|
||||
- console.log("You clicked!");
|
||||
+ window.addEventListener("click", () => {
|
||||
+ console.log("You knocked?");
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
-The `addEventListener` function registers its second argument to be called whenever the event described by its first argument occurs.
|
||||
+The `window` binding refers to a built-in object provided by the browser. It represents the browser window that contains the document. Calling its `addEventListener` method registers the second argument to be called whenever the event described by its first argument occurs.
|
||||
|
||||
## Events and DOM nodes
|
||||
|
||||
-Each browser event handler is registered in a context. When you call `addEventListener` as shown previously, you are calling it as a method on the whole window because in the browser the global scope is equivalent to the `window` object. Every DOM element has its own `addEventListener` method, which allows you to listen specifically on that element.
|
||||
+Each browser event handler is registered in a context. We called `addEventListener` on the `window` object before to register a handler for the whole window. Such a method can also be found on DOM elements and some other types of objects. Event listeners are only called when the event happens in the context of the object they are registered on.
|
||||
|
||||
```
|
||||
<button>Click me</button>
|
||||
<p>No handler here.</p>
|
||||
<script>
|
||||
- var button = document.querySelector("button");
|
||||
- button.addEventListener("click", function() {
|
||||
+ let button = document.querySelector("button");
|
||||
+ button.addEventListener("click", () => {
|
||||
console.log("Button clicked.");
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
-The example attaches a handler to the button node. Thus, clicks on the button cause that handler to run, whereas clicks on the rest of the document do not.
|
||||
+That example attaches a handler to the button node. Clicks on the button cause that handler to run, but clicks on the rest of the document do not.
|
||||
|
||||
-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.
|
||||
+Giving a node an `onclick` attribute has a similar effect. This works for most types of events—you can attach a handler through the attribute whose name is event name with `on` in front of it.
|
||||
|
||||
-The `removeEventListener` method, called with arguments similar to as `addEventListener`, removes a handler.
|
||||
+But a node can have 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 that it is safe to add handlers even if there is already another handler on the element.
|
||||
+
|
||||
+The `removeEventListener` method, called with arguments similar to `addEventListener`, removes a handler.
|
||||
|
||||
```
|
||||
<button>Act-once button</button>
|
||||
<script>
|
||||
- var button = document.querySelector("button");
|
||||
+ let button = document.querySelector("button");
|
||||
function once() {
|
||||
console.log("Done.");
|
||||
button.removeEventListener("click", once);
|
||||
@@ -60,51 +62,51 @@ The `removeEventListener` method, called with arguments similar to as `addEventL
|
||||
</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`.
|
||||
+The function given to `removeEventListener` has to be the exact same function value that was given to `addEventListener`. So to unregister a handler, you'll want to give the function a name (`once`, in the example) to be able to pass the same function value to both methods.
|
||||
|
||||
## 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.
|
||||
+Though we have ignored it so far, event handler functions are passed an argument: the _event object_. This object holds 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 `button` property.
|
||||
|
||||
```
|
||||
<button>Click me any way you want</button>
|
||||
<script>
|
||||
- var button = document.querySelector("button");
|
||||
- button.addEventListener("mousedown", function(event) {
|
||||
- if (event.which == 1)
|
||||
+ let button = document.querySelector("button");
|
||||
+ button.addEventListener("mousedown", event => {
|
||||
+ if (event.button == 0) {
|
||||
console.log("Left button");
|
||||
- else if (event.which == 2)
|
||||
+ } else if (event.button == 1) {
|
||||
console.log("Middle button");
|
||||
- else if (event.which == 3)
|
||||
+ } else if (event.button == 2) {
|
||||
console.log("Right button");
|
||||
+ }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
-The information stored in an event object differs per type of event. We'll discuss various types later in this chapter. The object's `type` property always holds a string identifying the event (for example `"click"` or `"mousedown"`).
|
||||
+The information stored in an event object differs per type of event. We'll discuss different types later in the chapter. The object's `type` property always holds a string identifying the event (such as `"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.
|
||||
+For most event types, handlers registered on nodes with children will also receive events that happen in the children. If a button inside a paragraph is clicked, event handlers on the paragraph will also see the click event.
|
||||
|
||||
But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to _propagate_ outward, from the node where it happened to that node's parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole window get a chance to respond to the event.
|
||||
|
||||
-At any point, an event handler can call the `stopPropagation` method on the event object to prevent handlers “further up” from receiving the event. This can be useful when, for example, you have a button inside another clickable element and you don't want clicks on the button to activate the outer element's click behavior.
|
||||
+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.
|
||||
|
||||
```
|
||||
<p>A paragraph with a <button>button</button>.</p>
|
||||
<script>
|
||||
- var para = document.querySelector("p");
|
||||
- var button = document.querySelector("button");
|
||||
- para.addEventListener("mousedown", function() {
|
||||
+ let para = document.querySelector("p");
|
||||
+ let button = document.querySelector("button");
|
||||
+ para.addEventListener("mousedown", () => {
|
||||
console.log("Handler for paragraph.");
|
||||
});
|
||||
- button.addEventListener("mousedown", function(event) {
|
||||
+ button.addEventListener("mousedown", event => {
|
||||
console.log("Handler for button.");
|
||||
- if (event.which == 3)
|
||||
- event.stopPropagation();
|
||||
+ if (event.button == 2) event.stopPropagation();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@@ -118,9 +120,10 @@ It is also possible to use the `target` property to cast a wide net for a specif
|
||||
<button>B</button>
|
||||
<button>C</button>
|
||||
<script>
|
||||
- document.body.addEventListener("click", function(event) {
|
||||
- if (event.target.nodeName == "BUTTON")
|
||||
+ document.body.addEventListener("click", event => {
|
||||
+ if (event.target.nodeName == "BUTTON") {
|
||||
console.log("Clicked", event.target.textContent);
|
||||
+ }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@@ -129,94 +132,83 @@ It is also possible to use the `target` property to cast a wide net for a specif
|
||||
|
||||
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.
|
||||
+For most types of events, the JavaScript event handlers are called _before_ the default behavior takes place. If the handler doesn't want this 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:
|
||||
|
||||
```
|
||||
<a href="https://developer.mozilla.org/">MDN</a>
|
||||
<script>
|
||||
- var link = document.querySelector("a");
|
||||
- link.addEventListener("click", function(event) {
|
||||
+ let link = document.querySelector("a");
|
||||
+ link.addEventListener("click", 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.
|
||||
+Try not to do such things unless you have a really good reason to. It'll be unpleasant for people who use your page when expected behavior 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.
|
||||
+Depending on the browser, some events can't be intercepted at all. On Chrome, for example, the keyboard shortcut to close the current tab (Ctrl-W or Command-W) cannot be handled by JavaScript.
|
||||
|
||||
## Key events
|
||||
|
||||
-When a key on the keyboard is pressed, your browser fires a `"keydown"` event. When it is released, a `"keyup"` event fires.
|
||||
+When a key on the keyboard is pressed down, your browser fires a `"keydown"` event. When it is released again, you get a `"keyup"` event.
|
||||
|
||||
```
|
||||
<p>This page turns violet when you hold the V key.</p>
|
||||
<script>
|
||||
- addEventListener("keydown", function(event) {
|
||||
- if (event.keyCode == 86)
|
||||
+ window.addEventListener("keydown", event => {
|
||||
+ if (event.key == "v") {
|
||||
document.body.style.background = "violet";
|
||||
+ }
|
||||
});
|
||||
- addEventListener("keyup", function(event) {
|
||||
- if (event.keyCode == 86)
|
||||
+ window.addEventListener("keyup", event => {
|
||||
+ if (event.key == "v") {
|
||||
document.body.style.background = "";
|
||||
+ }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
-Despite its name, `"keydown"` fires not only when the key is physically pushed down. When a key is pressed and held, the event fires again every time the key _repeats_. Sometimes—for example if you want to increase the acceleration of a game character when an arrow key is pressed and decrease it again when the key is released—you have to be careful not to increase it again every time the key repeats or you'd end up with unintentionally huge values.
|
||||
-
|
||||
-The previous example looked at the `keyCode` property of the event object. This is how you can identify which key is being pressed or released. Unfortunately, it's not always obvious how to translate the numeric key code to an actual key.
|
||||
-
|
||||
-For letter and number keys, the associated key code will be the Unicode character code associated with the (uppercase) letter or number printed on the key. The `charCodeAt` method on strings gives us a way to find this code.
|
||||
+Despite its name, `"keydown"` fires not only when the key is physically pushed down. When a key is pressed and held, the event fires again every time the key _repeats_. Sometimes you have to be careful about this. For example if you add a button to the DOM when a key is pressed down, and remove it again when the key is released, you might accidentally add hundreds of buttons when the key is held down longer.
|
||||
|
||||
-```
|
||||
-console.log("Violet".charCodeAt(0));
|
||||
-// → 86
|
||||
-console.log("1".charCodeAt(0));
|
||||
-// → 49
|
||||
-```
|
||||
-
|
||||
-Other keys have less predictable key codes. The best way to find the codes you need is usually by experimenting—register a key event handler that logs the key codes it gets and press the key you are interested in.
|
||||
+The example looked at the `key` property of the event object to see which key the event is about. This property holds a string that, for most keys, corresponds to the thing that pressing that key would type. For special keys like Enter, it holds a string that names the key (`"Enter"`, in this case). If you hold shift while pressing a key, that might also influence the name of the key—`"v"` becomes `"V"`, `"1"` may become `"!"`, if that is what pressing Shift-1 produces on your keyboard.
|
||||
|
||||
Modifier keys such as Shift, Ctrl, Alt, and Meta (Command on Mac) generate key events just like normal keys. But when looking for key combinations, you can also find out whether these keys are held down by looking at the `shiftKey`, `ctrlKey`, `altKey`, and `metaKey` properties of keyboard and mouse events.
|
||||
|
||||
```
|
||||
<p>Press Ctrl-Space to continue.</p>
|
||||
<script>
|
||||
- addEventListener("keydown", function(event) {
|
||||
- if (event.keyCode == 32 && event.ctrlKey)
|
||||
+ window.addEventListener("keydown", event => {
|
||||
+ if (event.key == " " && event.ctrlKey) {
|
||||
console.log("Continuing!");
|
||||
+ }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
-The `"keydown"` and `"keyup"` events give you information about the physical key that is being pressed. But what if you are interested in the actual text being typed? Getting that text from key codes is awkward. Instead, there exists another event, `"keypress"`, which fires right after `"keydown"` (and repeated along with `"keydown"` when the key is held) but only for keys that produce character input. The `charCode` property in the event object contains a code that can be interpreted as a Unicode character code. We can use the `String.fromCharCode` function to turn this code into an actual single-character string.
|
||||
+The DOM node where a key event originates depends on the element that has focus when the key is pressed. Most nodes cannot have focus unless you give them a `tabindex` attribute, but things like links, buttons, and form fields can. We'll come back to form fields in [Chapter 18](18_http.html#forms). When nothing in particular has focus, `document.body` acts as the target node of key events.
|
||||
|
||||
-```
|
||||
-<p>Focus this page and type something.</p>
|
||||
-<script>
|
||||
- addEventListener("keypress", function(event) {
|
||||
- console.log(String.fromCharCode(event.charCode));
|
||||
- });
|
||||
-</script>
|
||||
-```
|
||||
+When the user is typing text, using key events to figure out what is being typed is problematic. Some platforms, most notably the virtual keyboard on Android phones, don't fire key events. But even when you have an old-fashioned keyboard, some types of text input don't match key presses in a straightforward way, such as IME (“Input Method Editor”) software used by people whose scripts don't fit on a keyboard, where multiple key strokes are combined to create characters.
|
||||
+
|
||||
+To notice when something was typed, elements that you can type into, such as the `<input>` and `<textarea>` tags, fire `"input"` events whenever the user changed their content. To get the actual content that was typed, it is best to directly read it from the focused field. [Chapter 18](18_http.html#forms) will show how.
|
||||
+
|
||||
+## Pointer events
|
||||
|
||||
-The DOM node where a key event originates depends on the element that has focus when the key is pressed. Normal nodes cannot have focus (unless you give them a `tabindex` attribute), but things such as links, buttons, and form fields can. We'll come back to form fields in [Chapter 18](18_forms.html#forms). When nothing in particular has focus, `document.body` acts as the target node of key events.
|
||||
+There are currently two widely used ways to point at things on a screen: mice (including devices that act like mice, such as touchpads and trackballs) and touchscreens. These produce different kinds of events.
|
||||
|
||||
-## Mouse clicks
|
||||
+### Mouse clicks
|
||||
|
||||
-Pressing a mouse button also causes a number of events to fire. The `"mousedown"` and `"mouseup"` events are similar to `"keydown"` and `"keyup"` and fire when the button is pressed and released. These will happen on the DOM nodes that are immediately below the mouse pointer when the event occurs.
|
||||
+Pressing a mouse button causes a number of events to fire. The `"mousedown"` and `"mouseup"` events are similar to `"keydown"` and `"keyup"` and fire when the button is pressed and released. These happen on the DOM nodes that are immediately below the mouse pointer when the event occurs.
|
||||
|
||||
After the `"mouseup"` event, a `"click"` event fires on the most specific node that contained both the press and the release of the button. For example, if I press down the mouse button on one paragraph and then move the pointer to another paragraph and release the button, the `"click"` event will happen on the element that contains both those paragraphs.
|
||||
|
||||
If two clicks happen close together, a `"dblclick"` (double-click) event also fires, after the second click event.
|
||||
|
||||
-To get precise information about the place where a mouse event happened, you can look at its `pageX` and `pageY` properties, which contain the event's coordinates (in pixels) relative to the top-left corner of the document.
|
||||
+To get precise information about the place where a mouse event happened, you can look at its `clientX` and `clientY` properties, which contain the event's coordinates (in pixels) relative to the top-left corner of the window, or `pageX` and `pageY`, which are relative to the top-left corner of the whole document (which may be different, when the window has been scrolled).
|
||||
|
||||
-The following implements a primitive drawing program. Every time you click the document, it adds a dot under your mouse pointer. See [Chapter 19](19_paint.html#paint) for a less primitive drawing program.
|
||||
+The following implements a primitive drawing program. Every time you click the document, it adds a dot under your mouse pointer. See [Chapter 19](19_paint.html) for a less primitive drawing program.
|
||||
|
||||
```
|
||||
<style>
|
||||
@@ -232,8 +224,8 @@ The following implements a primitive drawing program. Every time you click the d
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
- addEventListener("click", function(event) {
|
||||
- var dot = document.createElement("div");
|
||||
+ window.addEventListener("click", event => {
|
||||
+ let dot = document.createElement("div");
|
||||
dot.className = "dot";
|
||||
dot.style.left = (event.pageX - 4) + "px";
|
||||
dot.style.top = (event.pageY - 4) + "px";
|
||||
@@ -242,11 +234,9 @@ The following implements a primitive drawing program. Every time you click the d
|
||||
</script>
|
||||
```
|
||||
|
||||
-The `clientX` and `clientY` properties are similar to `pageX` and `pageY` but relative to the part of the document that is currently scrolled into view. These can be useful when comparing mouse coordinates with the coordinates returned by `getBoundingClientRect`, which also returns viewport-relative coordinates.
|
||||
+### Mouse motion
|
||||
|
||||
-## Mouse motion
|
||||
-
|
||||
-Every time the mouse pointer moves, a `"mousemove"` event fires. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-dragging functionality.
|
||||
+Every time the mouse pointer moves, a `"mousemove"` event is fired. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-dragging functionality.
|
||||
|
||||
As an example, the following program displays a bar and sets up event handlers so that dragging to the left or right on this bar makes it narrower or wider:
|
||||
|
||||
@@ -255,119 +245,115 @@ As an example, the following program displays a bar and sets up event handlers s
|
||||
<div style="background: orange; width: 60px; height: 20px">
|
||||
</div>
|
||||
<script>
|
||||
- var lastX; // Tracks the last observed mouse X position
|
||||
- var rect = document.querySelector("div");
|
||||
- rect.addEventListener("mousedown", function(event) {
|
||||
- if (event.which == 1) {
|
||||
- lastX = event.pageX;
|
||||
- addEventListener("mousemove", moved);
|
||||
+ let lastX; // Tracks the last observed mouse X position
|
||||
+ let bar = document.querySelector("div");
|
||||
+ bar.addEventListener("mousedown", event => {
|
||||
+ if (event.button == 0) {
|
||||
+ lastX = event.clientX;
|
||||
+ window.addEventListener("mousemove", moved);
|
||||
event.preventDefault(); // Prevent selection
|
||||
}
|
||||
});
|
||||
|
||||
- function buttonPressed(event) {
|
||||
- if (event.buttons == null)
|
||||
- return event.which != 0;
|
||||
- else
|
||||
- return event.buttons != 0;
|
||||
- }
|
||||
function moved(event) {
|
||||
- if (!buttonPressed(event)) {
|
||||
- removeEventListener("mousemove", moved);
|
||||
+ if (event.buttons == 0) {
|
||||
+ window.removeEventListener("mousemove", moved);
|
||||
} else {
|
||||
- var dist = event.pageX - lastX;
|
||||
- var newWidth = Math.max(10, rect.offsetWidth + dist);
|
||||
- rect.style.width = newWidth + "px";
|
||||
- lastX = event.pageX;
|
||||
+ let dist = event.clientX - lastX;
|
||||
+ let newWidth = Math.max(10, bar.offsetWidth + dist);
|
||||
+ bar.style.width = newWidth + "px";
|
||||
+ lastX = event.clientX;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
-Note that the `"mousemove"` handler is registered on the whole window. Even if the mouse goes outside of the bar during resizing, we still want to update its size and stop dragging when the mouse is released.
|
||||
+Note that the `"mousemove"` handler is registered on the whole window. Even if the mouse goes outside of the bar during resizing, as long as the button is held we still want to update its size.
|
||||
|
||||
-We must stop resizing the bar when the mouse button is released. Unfortunately, not all browsers give `"mousemove"` events a meaningful `which` property. There is a standard property called `buttons`, which provides similar information, but that is also not supported on all browsers. Fortunately, all major browsers support either `buttons` or `which`, so the `buttonPressed` function in the example first tries `buttons`, and falls back to `which` when that isn't available.
|
||||
+We must stop resizing the bar when the mouse button is released. For that, we can use the `buttons` property (note the plural), which tells us about the buttons that are currently held down. When this is zero, no buttons are down. When buttons are held, its value is the sum of the codes for those buttons—the left button has code 1, the right button 2, and the middle one 4\. That way, you can check if a given button is pressed by taking the remainder of the value of `buttons` and its code.
|
||||
|
||||
-Whenever the mouse pointer enters or leaves a node, a `"mouseover"` or `"mouseout"` event fires. These two events can be used, among other things, to create hover effects, showing or styling something when the mouse is over a given element.
|
||||
+Note that the order of these codes is different from the one used by `button`, where the middle button came before the right one. As mentioned, consistency isn't really a strong point of the browser's programming interface.
|
||||
|
||||
-Unfortunately, creating such an effect is not as simple as starting the effect on `"mouseover"` and ending it on `"mouseout"`. When the mouse moves from a node onto one of its children, `"mouseout"` fires on the parent node, though the mouse did not actually leave the node's extent. To make things worse, these events propagate just like other events, and thus you will also receive `"mouseout"` events when the mouse leaves one of the child nodes of the node on which the handler is registered.
|
||||
+### Touch events
|
||||
|
||||
-To work around this problem, we can use the `relatedTarget` property of the event objects created for these events. It tells us, in the case of `"mouseover"`, what element the pointer was over before and, in the case of `"mouseout"`, what element it is going to. We want to change our hover effect only when the `relatedTarget` is outside of our target node. Only in that case does this event actually represent a _crossing over_ from outside to inside the node (or the other way around).
|
||||
+The style of graphical browser that we use was designed with mouse interfaces in mind, at a time where touchscreens were very rare. To make the Web “work” on early touchscreen phones, browsers for those devices pretended, to a certain extent, that touch events were mouse events. If you tap your screen, you'll get `"mousedown"`, `"mouseup"`, and `"click"` events.
|
||||
|
||||
-```
|
||||
-<p>Hover over this <strong>paragraph</strong>.</p>
|
||||
-<script>
|
||||
- var para = document.querySelector("p");
|
||||
- function isInside(node, target) {
|
||||
- for (; node != null; node = node.parentNode)
|
||||
- if (node == target) return true;
|
||||
- }
|
||||
- para.addEventListener("mouseover", function(event) {
|
||||
- if (!isInside(event.relatedTarget, para))
|
||||
- para.style.color = "red";
|
||||
- });
|
||||
- para.addEventListener("mouseout", function(event) {
|
||||
- if (!isInside(event.relatedTarget, para))
|
||||
- para.style.color = "";
|
||||
- });
|
||||
-</script>
|
||||
-```
|
||||
+But this illusion isn't very robust. A touchscreen works differently from a mouse: it doesn't have multiple buttons, you can't track the finger when it isn't on the screen (to simulate `"mousemove"`), and it allows multiple fingers to be on the screen at the same time.
|
||||
+
|
||||
+Mouse events only cover touch interaction in straightforward cases—if you add a `"click"` handler to a button, touch users will still be able to use it. But something like the resizeable bar in the last example does not work on a touchscreen.
|
||||
|
||||
-The `isInside` function follows the given node's parent links until it either reaches the top of the document (when `node` becomes null) or finds the parent we are looking for.
|
||||
+There are specific event types fired by touch interaction. When a finger starts touching the screen, you get a `"touchstart"` event. When it is moved while touching, `"touchmove"` events fire. And finally, when it stops touching the screen, you'll see a `"touchend"` event.
|
||||
|
||||
-I should add that a hover effect like this can be much more easily achieved using the CSS _pseudoselector_ `:hover`, as the next example shows. But when your hover effect involves doing something more complicated than changing a style on the target node, you must use the trick with `"mouseover"` and `"mouseout"` events.
|
||||
+Because many touchscreens can detect multiple fingers at the same time, these events don't have a single set of coordinates associated with them. Rather, their event objects have a `touches` property, which holds an array-like object of points, each of which has its own `clientX`, `clientY`, `pageX`, and `pageY` properties.
|
||||
+
|
||||
+You could do something like this to show red circles around every touching finger.
|
||||
|
||||
```
|
||||
<style>
|
||||
- p:hover { color: red }
|
||||
+ dot { position: absolute; display: block;
|
||||
+ border: 2px solid red; border-radius: 50px;
|
||||
+ height: 100px; width: 100px; }
|
||||
</style>
|
||||
-<p>Hover over this <strong>paragraph</strong>.</p>
|
||||
+<p>Touch this page</p>
|
||||
+<script>
|
||||
+ function update(event) {
|
||||
+ for (let dot; dot = document.querySelector("dot");) {
|
||||
+ dot.remove();
|
||||
+ }
|
||||
+ for (let i = 0; i < event.touches.length; i++) {
|
||||
+ let {pageX, pageY} = event.touches[i];
|
||||
+ let dot = document.createElement("dot");
|
||||
+ dot.style.left = (pageX - 50) + "px";
|
||||
+ dot.style.top = (pageY - 50) + "px";
|
||||
+ document.body.appendChild(dot);
|
||||
+ }
|
||||
+ }
|
||||
+ window.addEventListener("touchstart", update);
|
||||
+ window.addEventListener("touchmove", update);
|
||||
+ window.addEventListener("touchend", update);
|
||||
+</script>
|
||||
```
|
||||
|
||||
+You'll often want to call `preventDefault` in touch event handlers, to override the browser's default behavior (which may include scrolling the page on swiping) and to prevent the mouse events from being fired, for which you may _also_ have a handler.
|
||||
+
|
||||
## Scroll events
|
||||
|
||||
-Whenever an element is scrolled, a `"scroll"` event fires on it. This has various uses, such as knowing what the user is currently looking at (for disabling off-screen animations or sending spy reports to your evil headquarters) or showing some indication of progress (by highlighting part of a table of contents or showing a page number).
|
||||
+Whenever an element is scrolled, a `"scroll"` event is fired on it. This has various uses, such as knowing what the user is currently looking at (for disabling off-screen animations or sending spy reports to your evil headquarters) or showing some indication of progress (by highlighting part of a table of contents or showing a page number).
|
||||
|
||||
-The following example draws a progress bar in the top-right corner of the document and updates it to fill up as you scroll down:
|
||||
+The following example draws a progress bar above the document and updates it to fill up as you scroll down:
|
||||
|
||||
```
|
||||
<style>
|
||||
- .progress {
|
||||
- border: 1px solid blue;
|
||||
- width: 100px;
|
||||
+ #progress {
|
||||
+ border-bottom: 2px solid blue;
|
||||
+ width: 0;
|
||||
position: fixed;
|
||||
- top: 10px; right: 10px;
|
||||
- }
|
||||
- .progress > div {
|
||||
- height: 12px;
|
||||
- background: blue;
|
||||
- width: 0%;
|
||||
- }
|
||||
- body {
|
||||
- height: 2000px;
|
||||
+ top: 0; left: 0;
|
||||
}
|
||||
</style>
|
||||
-<div class="progress"><div></div></div>
|
||||
-<p>Scroll me...</p>
|
||||
+<div id="progress"></div>
|
||||
<script>
|
||||
- var bar = document.querySelector(".progress div");
|
||||
- addEventListener("scroll", function() {
|
||||
- var max = document.body.scrollHeight - innerHeight;
|
||||
- var percent = (pageYOffset / max) * 100;
|
||||
- bar.style.width = percent + "%";
|
||||
+ // Create some content
|
||||
+ document.body.appendChild(document.createTextNode(
|
||||
+ "supercalifragilisticexpialidocious ".repeat(1000)));
|
||||
+
|
||||
+ let bar = document.querySelector("#progress");
|
||||
+ window.addEventListener("scroll", () => {
|
||||
+ let max = document.body.scrollHeight - innerHeight;
|
||||
+ bar.style.width = `${(pageYOffset / max) * 100}%`;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
-Giving an element a `position` of `fixed` acts much like an `absolute` position but also prevents it from scrolling along with the rest of the document. The effect is to make our progress bar stay in its corner. Inside it is another element, which is resized to indicate the current progress. We use `%`, rather than `px`, as a unit when setting the width so that the element is sized relative to the whole bar.
|
||||
+Giving an element a `position` of `fixed` acts much like an `absolute` position but also prevents it from scrolling along with the rest of the document. The effect is to make our progress bar stay at the top. Its width is changed to indicate the current progress. We use `%`, rather than `px`, as a unit when setting the width so that the element is sized relative to the page width.
|
||||
|
||||
-The global `innerHeight` variable gives us the height of the window, which we have to subtract from the total scrollable height—you can't keep scrolling when you hit the bottom of the document. (There's also an `innerWidth` to go along with `innerHeight`.) By dividing `pageYOffset`, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar.
|
||||
+The global `innerHeight` binding gives us the height of the window, which we have to subtract from the total scrollable height—you can't keep scrolling when you hit the bottom of the document. There's also an `innerWidth`, for the window width. By dividing `pageYOffset`, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar.
|
||||
|
||||
Calling `preventDefault` on a scroll event does not prevent the scrolling from happening. In fact, the event handler is called only _after_ the scrolling takes place.
|
||||
|
||||
## Focus events
|
||||
|
||||
-When an element gains focus, the browser fires a `"focus"` event on it. When it loses focus, a `"blur"` event fires.
|
||||
+When an element gains focus, the browser fires a `"focus"` event on it. When it loses focus, the element gets a `"blur"` event.
|
||||
|
||||
Unlike the events discussed earlier, these two events do not propagate. A handler on a parent element is not notified when a child element gains or loses focus.
|
||||
|
||||
@@ -375,18 +361,18 @@ The following example displays help text for the text field that currently has f
|
||||
|
||||
```
|
||||
<p>Name: <input type="text" data-help="Your full name"></p>
|
||||
-<p>Age: <input type="text" data-help="Age in years"></p>
|
||||
+<p>Age: <input type="text" data-help="Your age in years"></p>
|
||||
<p id="help"></p>
|
||||
|
||||
<script>
|
||||
- var help = document.querySelector("#help");
|
||||
- var fields = document.querySelectorAll("input");
|
||||
- for (var i = 0; i < fields.length; i++) {
|
||||
- fields[i].addEventListener("focus", function(event) {
|
||||
- var text = event.target.getAttribute("data-help");
|
||||
+ let help = document.querySelector("#help");
|
||||
+ let fields = document.querySelectorAll("input");
|
||||
+ for (let field of Array.from(fields)) {
|
||||
+ field.addEventListener("focus", event => {
|
||||
+ let text = event.target.getAttribute("data-help");
|
||||
help.textContent = text;
|
||||
});
|
||||
- fields[i].addEventListener("blur", function(event) {
|
||||
+ field.addEventListener("blur", event => {
|
||||
help.textContent = "";
|
||||
});
|
||||
}
|
||||
@@ -397,60 +383,51 @@ The window object will receive `"focus"` and `"blur"` events when the user moves
|
||||
|
||||
## Load event
|
||||
|
||||
-When a page finishes loading, the `"load"` event fires on the window and the document body objects. This is often used to schedule initialization actions that require the whole document to have been built. Remember that the content of `<script>` tags is run immediately when the tag is encountered. This is often too soon, such as when the script needs to do something with parts of the document that appear after the `<script>` tag.
|
||||
+When a page finishes loading, the `"load"` event fires on the window and the document body objects. This is often used to schedule initialization actions that require the whole document to have been built. Remember that the content of `<script>` tags is run immediately when the tag is encountered. This may be too soon, for example when the script needs to do something with parts of the document that appear after the `<script>` tag.
|
||||
|
||||
Elements such as images and script tags that load an external file also have a `"load"` event that indicates the files they reference were loaded. Like the focus-related events, loading events do not propagate.
|
||||
|
||||
-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. Preventing the page from unloading is not, as you might expect, done with the `preventDefault` method. Instead, it is done by returning a string from the handler. The string will be used in a dialog that asks the user if they want to stay on the page or leave it. This mechanism ensures that a user is able to leave the page, even if it is running a malicious script that would prefer to keep them there forever in order to force them to look at dodgy weight loss ads.
|
||||
+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. Preventing the page from unloading is not, as you might expect, done with the `preventDefault` method. Instead, it is done by returning a non-null value from the handler. When you do that, the browser will show the user a dialog asking if are sure they want to leave the page. This mechanism ensures that a user is always able to leave, even on malicious pages that would prefer to keep them there forever and force them to look at dodgy weight loss ads.
|
||||
|
||||
-## Script execution timeline
|
||||
+## Events and the event loop
|
||||
|
||||
-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](13_dom.html#animationFrame) 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.
|
||||
+In the context of the event loop, as discussed in [Chapter 11](11_async.html), browser event handlers behave like other asynchronous notifications. They are scheduled when the event occurs, but must wait for other scripts that are running to finish before they get a chance to run.
|
||||
|
||||
-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. The browser cannot react to clicks and other events inside the document because it can't run event handlers until the current script finishes running.
|
||||
+The fact that events can only be processed when nothing else is running means that, if the event loop is tied up with other work, any interaction with the page (which happens through events) will be delayed until there's time to process it. So if you schedule too much work, either with long-running event handlers or with lots of short-running ones, the page will become slow and cumbersome to use.
|
||||
|
||||
-Some programming environments do allow multiple _threads of execution_ to run at the same time. Doing multiple things at the same time can be used to make a program faster. But when you have multiple actors touching the same parts of the system at the same time, thinking about a program becomes at least an order of magnitude harder.
|
||||
+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 a JavaScript process that runs alongside the main script, on its own timeline.
|
||||
|
||||
-The fact that JavaScript programs do only one thing at a time makes our lives easier. 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.
|
||||
-
|
||||
-Assume we have the following code in a file called `code/squareworker.js`:
|
||||
+Imagine that squaring a number is a heavy, long-running computation that we want to perform in a separate thread. We could write a file called `code/<wbr>squareworker.<wbr>js` that responds to messages by computing a square and sending a message back:
|
||||
|
||||
```
|
||||
-addEventListener("message", function(event) {
|
||||
+addEventListener("message", event => {
|
||||
postMessage(event.data * event.data);
|
||||
});
|
||||
```
|
||||
|
||||
-Imagine that squaring a number is a heavy, long-running computation that we want to perform in a background thread. This code spawns a worker, sends it a few messages, and outputs the responses.
|
||||
+To avoid the problems of having multiple threads touching the same data, workers do not share their global scope or any other data with the main script's environment. Instead, you have to communicate with them by sending messages back and forth.
|
||||
+
|
||||
+This code spawns a worker running that script, sends it a few messages, and outputs the responses.
|
||||
|
||||
```
|
||||
-var squareWorker = new Worker("code/squareworker.js");
|
||||
-squareWorker.addEventListener("message", function(event) {
|
||||
+let squareWorker = new Worker("code/squareworker.js");
|
||||
+squareWorker.addEventListener("message", event => {
|
||||
console.log("The worker responded:", event.data);
|
||||
});
|
||||
squareWorker.postMessage(10);
|
||||
squareWorker.postMessage(24);
|
||||
```
|
||||
|
||||
-The `postMessage` function sends a message, which will cause a `"message"` event to fire in the receiver. The script that created the worker sends and receives messages through the `Worker` object, whereas the worker talks to the script that created it by sending and listening directly on its global scope—which is a _new_ global scope, not shared with the original script.
|
||||
+The `postMessage` function sends a message, which will cause a `"message"` event to fire in the receiver. The script that created the worker sends and receives messages through the `Worker` object, whereas the worker talks to the script that created it by sending and listening directly on its global scope. Only values that can be represented as JSON can be sent as messages—the other side will receive a _copy_ of them, rather than the value itself.
|
||||
|
||||
-## Setting timers
|
||||
+## Timers
|
||||
|
||||
-The `setTimeout` function is similar to `requestAnimationFrame`. It schedules another function to be called later. But instead of calling the function at the next redraw, it waits for a given amount of milliseconds. This page turns from blue to yellow after two seconds:
|
||||
-
|
||||
-```
|
||||
-<script>
|
||||
- document.body.style.background = "blue";
|
||||
- setTimeout(function() {
|
||||
- document.body.style.background = "yellow";
|
||||
- }, 2000);
|
||||
-</script>
|
||||
-```
|
||||
+We saw the `setTimeout` function in [Chapter 11](11_async.html). It schedules another function to be called later, after a given amount of milliseconds.
|
||||
|
||||
Sometimes you need to cancel a function you have scheduled. This is done by storing the value returned by `setTimeout` and calling `clearTimeout` on it.
|
||||
|
||||
```
|
||||
-var bombTimer = setTimeout(function() {
|
||||
+let bombTimer = setTimeout(() => {
|
||||
console.log("BOOM!");
|
||||
}, 500);
|
||||
|
||||
@@ -462,11 +439,11 @@ if (Math.random() < 0.5) { // 50% chance
|
||||
|
||||
The `cancelAnimationFrame` function works in the same way as `clearTimeout`—calling it on a value returned by `requestAnimationFrame` will cancel that frame (assuming it hasn't already been called).
|
||||
|
||||
-A similar set of functions, `setInterval` and `clearInterval` are used to set timers that should repeat every _X_ milliseconds.
|
||||
+A similar set of functions, `setInterval` and `clearInterval` are used to set timers that should _repeat_ every _X_ milliseconds.
|
||||
|
||||
```
|
||||
-var ticks = 0;
|
||||
-var clock = setInterval(function() {
|
||||
+let ticks = 0;
|
||||
+let clock = setInterval(() => {
|
||||
console.log("tick", ticks++);
|
||||
if (ticks == 10) {
|
||||
clearInterval(clock);
|
||||
@@ -477,22 +454,20 @@ var clock = setInterval(function() {
|
||||
|
||||
## 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.
|
||||
+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.
|
||||
|
||||
If you do need to do something nontrivial in such a handler, you can use `setTimeout` to make sure you are not doing it too often. This is usually called _debouncing_ the event. There are several slightly different approaches to this.
|
||||
|
||||
-In the first example, we want to do something when the user has typed something, but we don't want to do it immediately for every key event. When they are typing quickly, we just want to wait until a pause occurs. Instead of immediately performing an action in the event handler, we set a timeout instead. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled.
|
||||
+In the first example, we want to react when the user has typed something, but we don't want to do it immediately for every input event. When they are typing quickly, we just want to wait until a pause occurs. Instead of immediately performing an action in the event handler, we set a timeout. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled.
|
||||
|
||||
```
|
||||
<textarea>Type something here...</textarea>
|
||||
<script>
|
||||
- var textarea = document.querySelector("textarea");
|
||||
- var timeout;
|
||||
- textarea.addEventListener("keydown", function() {
|
||||
+ let textarea = document.querySelector("textarea");
|
||||
+ let timeout;
|
||||
+ textarea.addEventListener("input", () => {
|
||||
clearTimeout(timeout);
|
||||
- timeout = setTimeout(function() {
|
||||
- console.log("You stopped typing.");
|
||||
- }, 500);
|
||||
+ timeout = setTimeout(() => console.log("Typed!"), 500);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@@ -503,72 +478,65 @@ We can use a slightly different pattern if we want to space responses so that th
|
||||
|
||||
```
|
||||
<script>
|
||||
- function displayCoords(event) {
|
||||
- document.body.textContent =
|
||||
- "Mouse at " + event.pageX + ", " + event.pageY;
|
||||
- }
|
||||
-
|
||||
- var scheduled = false, lastEvent;
|
||||
- addEventListener("mousemove", function(event) {
|
||||
- lastEvent = event;
|
||||
+ let scheduled = null;
|
||||
+ window.addEventListener("mousemove", event => {
|
||||
if (!scheduled) {
|
||||
- scheduled = true;
|
||||
- setTimeout(function() {
|
||||
- scheduled = false;
|
||||
- displayCoords(lastEvent);
|
||||
+ setTimeout(() => {
|
||||
+ document.body.textContent =
|
||||
+ `Mouse at ${scheduled.pageX}, ${scheduled.pageY}`;
|
||||
+ scheduled = null;
|
||||
}, 250);
|
||||
}
|
||||
+ scheduled = event;
|
||||
});
|
||||
</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.
|
||||
+Event handlers make it possible to detect and react to events happening in our web page. 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.
|
||||
+Pressing a key fires `"keydown"` and `"keyup"` events. Pressing a mouse button fires `"mousedown"`, `"mouseup"`, and `"click"` events. Moving the mouse fires `"mousemove"` events. Touchscreen interaction will result in `"touchstart"`, `"touchmove"`, and `"touchend"` 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. Thus, event handlers and other scheduled scripts have to wait until other scripts finish before they get their turn.
|
||||
-
|
||||
## Exercises
|
||||
|
||||
-### Censored keyboard
|
||||
+### Balloon
|
||||
|
||||
-Between 1928 and 2013, Turkish law forbade the use of the letters _Q_, _W_, and _X_ in official documents. This was part of a wider initiative to stifle Kurdish culture—those letters occur in the language used by Kurdish people but not in Istanbul Turkish.
|
||||
+Write a page that displays a balloon (using the balloon emoji, 🎈). When you press the up arrow, it should inflate (grow) ten percent, and when you press the down arrow, it should deflate (shrink) 10%.
|
||||
|
||||
-As an exercise in doing ridiculous things with technology, I'm asking you to program a text field (an `<input type="text">` tag) that these letters cannot be typed into.
|
||||
+You can control the size of text (emoji are text) by setting the `font-size` CSS property (`style.fontSize`) on its parent element. Remember to include a unit in the value, for example pixels (`10px`).
|
||||
|
||||
-(Do not worry about copy and paste and other such loopholes.)
|
||||
+The key names of the arrow keys are `"ArrowUp"` and `"ArrowDown"`. Make sure the keys only change the balloon, without scrolling the page.
|
||||
+
|
||||
+When that works, add a feature where, if you blow up the balloon past a certain size, it explodes. In this case, exploding means that it is replaced with an 💥 emoji, and the event handler is removed (so that you can't inflate or deflate the explosion).
|
||||
|
||||
```
|
||||
-<input type="text">
|
||||
+<p>🎈</p>
|
||||
+
|
||||
<script>
|
||||
- var field = document.querySelector("input");
|
||||
- // Your code here.
|
||||
+ // Your code here
|
||||
</script>
|
||||
```
|
||||
|
||||
-The solution to this exercise involves preventing the default behavior of key events. You can handle either `"keypress"` or `"keydown"`. If either of them has `preventDefault` called on it, the letter will not appear.
|
||||
+You'll want to register a handler for the `"keydown"` event, and look at `event.key` to figure out whether the up or down arrow key was pressed.
|
||||
|
||||
-Identifying the letter typed requires looking at the `keyCode` or `charCode` property and comparing that with the codes for the letters you want to filter. In `"keydown"`, you do not have to worry about lowercase and uppercase letters, since it identifies only the key pressed. If you decide to handle `"keypress"` instead, which identifies the actual character typed, you have to make sure you test for both cases. One way to do that would be this:
|
||||
+The current size can be kept in a binding, so that you can base the new size on it. It'll be helpful to define a function that updates the size—both the binding and the style of the balloon in the DOM, so that you can call it from your event handler, and possibly also once when starting, to set the initial size.
|
||||
|
||||
-```
|
||||
-/[qwx]/i.test(String.fromCharCode(event.charCode))
|
||||
-```
|
||||
+You can change the balloon to an explosion by replacing the text node with another one (using `replaceChild`), or by setting the `textContent` property of its parent node to a new string.
|
||||
|
||||
### Mouse trail
|
||||
|
||||
In JavaScript's early days, which was the high time of gaudy home pages with lots of animated images, people came up with some truly inspiring ways to use the language.
|
||||
|
||||
-One of these was the “mouse trail”—a series of images that would follow the mouse pointer as you moved it across the page.
|
||||
+One of these was the _mouse trail_—a series of elements that would follow the mouse pointer as you moved it across the page.
|
||||
|
||||
-In this exercise, I want you to implement a mouse trail. Use absolutely positioned `<div>` elements with a fixed size and background color (refer to the [code](14_event.html#mouse_drawing) in the “Mouse Clicks” section for an example). Create a bunch of such elements and, when the mouse moves, display them in the wake of the mouse pointer.
|
||||
+In this exercise, I want you to implement a mouse trail. Use absolutely positioned `<div>` elements with a fixed size and background color (refer to the [code](15_event.html#mouse_drawing) in the “Mouse Clicks” section for an example). Create a bunch of such elements and, when the mouse moves, display them in the wake of the mouse pointer.
|
||||
|
||||
There are various possible approaches here. You can make your solution as simple or as complex as you want. A simple solution to start with is to keep a fixed number of trail elements and cycle through them, moving the next one to the mouse's current position every time a `"mousemove"` event occurs.
|
||||
|
||||
@@ -590,38 +558,38 @@ There are various possible approaches here. You can make your solution as simple
|
||||
</script>
|
||||
```
|
||||
|
||||
-Creating the elements is best done in a loop. Append them to the document to make them show up. To be able to access them later to change their position, store the trail elements in an array.
|
||||
+Creating the elements is best done with a loop. Append them to the document to make them show up. To be able to access them later in order to change their position, you'll want to store the elements in an array.
|
||||
|
||||
-Cycling through them can be done by keeping a counter variable and adding 1 to it every time the `"mousemove"` event fires. The remainder operator (`% 10`) can then be used to get a valid array index to pick the element you want to position during a given event.
|
||||
+Cycling through them can be done by keeping a counter variable and adding 1 to it every time the `"mousemove"` event fires. The remainder operator (`% elements.<wbr>length`) can then be used to get a valid array index to pick the element you want to position during a given event.
|
||||
|
||||
-Another interesting effect can be achieved by modeling a simple physics system. Use the `"mousemove"` event only to update a pair of variables that track the mouse position. Then use `requestAnimationFrame` to simulate the trailing elements being attracted to the position of the mouse pointer. At every animation step, update their position based on their position relative to the pointer (and, optionally, a speed that is stored for each element). Figuring out a good way to do this is up to you.
|
||||
+Another interesting effect can be achieved by modeling a simple physics system. Use the `"mousemove"` event only to update a pair of bindings that track the mouse position. Then use `requestAnimationFrame` to simulate the trailing elements being attracted to the position of the mouse pointer. At every animation step, update their position based on their position relative to the pointer (and, optionally, a speed that is stored for each element). Figuring out a good way to do this is up to you.
|
||||
|
||||
### Tabs
|
||||
|
||||
-A tabbed interface is a common design pattern. It allows you to select an interface panel by choosing from a number of tabs “sticking out” above an element.
|
||||
+Tabbed panels are widely used in user interfaces. They allow you to select an interface panel by choosing from a number of tabs “sticking out” above an element.
|
||||
|
||||
-In this exercise you'll implement a simple tabbed interface. Write a function, `asTabs`, that takes a DOM node and creates a tabbed interface showing the child elements of that node. It should insert a list of `<button>` elements at the top of the node, one for each child element, containing text retrieved from the `data-tabname` attribute of the child. All but one of the original children should be hidden (given a `display` style of `none`), and the currently visible node can be selected by clicking the buttons.
|
||||
+In this exercise you must implement a simple tabbed interface. Write a function, `asTabs`, that takes a DOM node and creates a tabbed interface showing the child elements of that node. It should insert a list of `<button>` elements at the top of the node, one for each child element, containing text retrieved from the `data-tabname` attribute of the child. All but one of the original children should be hidden (given a `display` style of `none`). The currently visible node can be selected by clicking the buttons.
|
||||
|
||||
-When it works, extend it to also style the currently active button differently.
|
||||
+When that works, extend it to style the button for the currently selected tab differently, so that it is obvious which tab is selected.
|
||||
|
||||
```
|
||||
-<div id="wrapper">
|
||||
+<tab-panel>
|
||||
<div data-tabname="one">Tab one</div>
|
||||
<div data-tabname="two">Tab two</div>
|
||||
<div data-tabname="three">Tab three</div>
|
||||
-</div>
|
||||
+</tab-panel>
|
||||
<script>
|
||||
function asTabs(node) {
|
||||
// Your code here.
|
||||
}
|
||||
- asTabs(document.querySelector("#wrapper"));
|
||||
+ asTabs(document.querySelector("tab-panel"));
|
||||
</script>
|
||||
```
|
||||
|
||||
-One pitfall you'll probably run into is that you can't directly use the node's `childNodes` property as a collection of tab nodes. For one thing, when you add the buttons, they will also become child nodes and end up in this object because it is live. For another, the text nodes created for the whitespace between the nodes are also in there and should not get their own tabs.
|
||||
+One pitfall you might run into is that you can't directly use the node's `childNodes` property as a collection of tab nodes. For one thing, when you add the buttons, they will also become child nodes and end up in this object because it is a live data structure. For another, the text nodes created for the whitespace between the nodes are also in `childNodes`, but should not get their own tabs. You can use `children` instead of `childNodes` to ignore text nodes.
|
||||
|
||||
-To work around this, start by building up a real array of all the children in the wrapper that have a `nodeType` of 1.
|
||||
+You could start by building up an array of tabs, so that you have easy access to them. To implement the styling of the buttons, you could store objects that contain both tab panel and its button.
|
||||
|
||||
-When registering event handlers on the buttons, the handler functions will need to know which tab element is associated with the button. If they are created in a normal loop, you can access the loop index variable from inside the function, but it won't give you the correct number because that variable will have been further changed by the loop.
|
||||
+I recommend writing a separate function for changing tabs. You can either store the previously selected tab, and only change the styles needed to hide that and show the new one, or you can just update the style of all tabs every time a new tab is selected.
|
||||
|
||||
-A simple workaround is to use the `forEach` method and create the handler functions from inside the function passed to `forEach`. The loop index, which is passed as a second argument to that function, will be a normal local variable there and won't be overwritten by further iterations.
|
||||
+You might want to call this function immediately, to make the interface start with the first tab visible.
|
1236
diff-en/2ech15-3ech16.diff
Normal file
1236
diff-en/2ech15-3ech16.diff
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user