diff --git a/diff-en/2ech11-3ech12.diff b/diff-en/2ech11-3ech12.diff new file mode 100644 index 0000000..a3bb7bf --- /dev/null +++ b/diff-en/2ech11-3ech12.diff @@ -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. + +-![The structure of a syntax tree](img/syntax_tree.svg) ++
![The structure of a syntax tree](img/syntax_tree.svg)
+ + 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. diff --git a/diff-en/2ech12-3ech13.diff b/diff-en/2ech12-3ech13.diff new file mode 100644 index 0000000..d683956 --- /dev/null +++ b/diff-en/2ech12-3ech13.diff @@ -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: + + ``` + + + ++ + My home page + + +@@ -75,19 +80,19 @@ A simple HTML document looks like this: + + ``` + +-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: + ``` + + ++ + My home page + +

My home page

+@@ -104,11 +110,11 @@ The following document will be treated just like the one shown previously: + here. + ``` + +-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 + + ``` + +-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 `& + + ``` + +-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. + + ``` + + ``` + +-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. diff --git a/diff-en/2ech13-3ech14.diff b/diff-en/2ech13-3ech14.diff new file mode 100644 index 0000000..6285605 --- /dev/null +++ b/diff-en/2ech13-3ech14.diff @@ -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): + + ``` + +@@ -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: + +-![HTML document as nested boxes](img/html-boxes.svg) ++
![HTML document as nested boxes](img/html-boxes.svg)
+ + 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: + +-![HTML document as a tree](img/html-tree.svg) ++
![HTML document as a tree](img/html-tree.svg)
+ + 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: + +-![Links between DOM nodes](img/html-links.svg) ++
![Links between DOM nodes](img/html-links.svg)
+ +-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. + + ``` +

My ostrich Gertrude:

+

+ + + ``` +@@ -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. + + ``` +

One

+@@ -131,20 +140,20 @@ Almost everything about the DOM data structure can be changed. Element nodes hav +

Three

+ + + ``` + +-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. + + ``` +

The Cat in the +@@ -154,11 +163,11 @@ This involves not only removing the images but adding a new text node to replace + + + ``` + +-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. + + ``` +

+@@ -192,13 +200,11 @@ The following example defines a utility `elt`, which creates an element node and +
+ + +-``` +- +-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: +- ++ + ``` +-

Here it is, the identity function:

+-
+-function id(x) { return x; }
+-
+ +- +-``` ++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. + + ``` +

+@@ -303,17 +258,17 @@ The size and position of an element can be accessed from JavaScript. The `offset +

+ + + ``` + +-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. + + ``` +

+@@ -321,24 +276,24 @@ A program that repeatedly alternates between reading DOM layout information and + + +@@ -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: + + ``` +

Normal link

+@@ -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 inline, +@@ -365,23 +320,23 @@ This text is displayed inline, + not at all. + ``` + +-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. + + ``` +

+- Pretty text ++ Nice text +

+ + + ``` + +-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 +

Now strong text is italic and gray.

+ ``` + +-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 + + ``` + +-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: + + ``` +

+ +

+ + ``` + +-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. + +-![Using cosine and sine to compute coordinates](img/cos_sin.svg) ++
![Using cosine and sine to compute coordinates](img/cos_sin.svg)
+ +-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: + + ``` + + + + +- ++ + + + +@@ -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"`. + + ``` +- ++

Mountains

+ +- + ``` + +-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. + + ``` +

Heading with a span element.

+@@ -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 + + ``` + +-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. + + ``` ++ + + + + + ``` ++ ++`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. diff --git a/diff-en/2ech14-3ech15.diff b/diff-en/2ech14-3ech15.diff new file mode 100644 index 0000000..2bba450 --- /dev/null +++ b/diff-en/2ech14-3ech15.diff @@ -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. + + ``` +

Click this document to activate the handler.

+ + ``` + +-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. + + ``` + +

No handler here.

+ + ``` + +-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. + + ``` + + + ``` + +-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. + + ``` + + + ``` + +-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. + + ``` +

A paragraph with a .

+ + ``` +@@ -118,9 +120,10 @@ It is also possible to use the `target` property to cast a wide net for a specif + + + + ``` +@@ -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: + + ``` + MDN + + ``` + +-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. + + ``` +

This page turns violet when you hold the V key.

+ + ``` + +-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. + + ``` +

Press Ctrl-Space to continue.

+ + ``` + +-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. + +-``` +-

Focus this page and type something.

+- +-``` ++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. + + ``` + + + ``` + +-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 +
+
+ + ``` + +-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. + +-``` +-

Hover over this paragraph.

+- +-``` ++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. + + ``` + +-

Hover over this paragraph.

++

Touch this page

++ + ``` + ++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: + + ``` + +-
+-

Scroll me...

++
+ + ``` + +-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 + + ``` +

Name:

+-

Age:

++

Age:

+

+ + +-``` ++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. + + ``` + + + ``` +@@ -503,72 +478,65 @@ We can use a slightly different pattern if we want to space responses so that th + + ``` + + ``` + + ## 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). + + ``` +- ++

🎈

++ + + ``` + +-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 + + ``` + +-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. + + ``` +-
++ +
Tab one
+
Tab two
+
Tab three
+-
++ + + ``` + +-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. diff --git a/diff-en/2ech15-3ech16.diff b/diff-en/2ech15-3ech16.diff new file mode 100644 index 0000000..e03bf16 --- /dev/null +++ b/diff-en/2ech15-3ech16.diff @@ -0,0 +1,1236 @@ +diff --git a/2ech15.md b/3ech16.md +index 1007a10..560b459 100644 +--- a/2ech15.md ++++ b/3ech16.md +@@ -1,196 +1,246 @@ +-# Chapter 15Project: A Platform Game ++# Chapter 16Project: A Platform Game + + > All reality is a game. + > + > <footer>Iain Banks, <cite>The Player of Games</cite></footer> + +-My initial fascination with computers, like that of many kids, originated with computer games. I was drawn into the tiny computer-simulated worlds that I could manipulate and in which stories (sort of) unfolded—more, I suppose, because of the way I could project my imagination into them than because of the possibilities they actually offered. ++Much of my initial fascination with computers, like that of many nerdy kids, had to do with computer games. I was drawn into the tiny simulated worlds that I could manipulate and in which stories (sort of) unfolded—more, I suppose, because of the way I projected my imagination into them than because of the possibilities they actually offered. + +-I wouldn't wish a career in game programming on anyone. Much like the music industry, the discrepancy between the many eager young people wanting to work in it and the actual demand for such people creates a rather unhealthy environment. But writing games for fun is amusing. ++I don't wish a career in game programming on anyone. Much like the music industry, the discrepancy between the amount of eager young people wanting to work in it and the actual demand for such people creates a rather unhealthy environment. But writing games for fun is amusing. + +-This chapter will walk through the implementation of a simple platform game. Platform games (or “jump and run” games) are games that expect the player to move a figure through a world, which is often two-dimensional and viewed from the side, and do lots of jumping onto and over things. ++This chapter will walk through the implementation of a small platform game. Platform games (or “jump and run” games) are games that expect the player to move a figure through a world, which is usually two-dimensional and viewed from the side, jumping over and onto things. + + ## The game + +-Our game will be roughly based on [Dark Blue](http://www.lessmilk.com/games/10) by Thomas Palef. I chose this game because it is both entertaining and minimalist, and because it can be built without too much code. It looks like this: ++Our game will be roughly based on [Dark Blue](http://www.lessmilk.com/games/10) by Thomas Palef. I chose that game because it is both entertaining and minimalist, and because it can be built without too much code. It looks like this: + +-![The game Dark Blue](img/darkblue.png) ++
![The game Dark Blue](img/darkblue.png)
+ +-The dark box represents the player, whose task is to collect the yellow boxes (coins) while avoiding the red stuff (lava?). A level is completed when all coins have been collected. ++The dark box represents the player, whose task is to collect the yellow boxes (coins) while avoiding the red stuff (lava). A level is completed when all coins have been collected. + +-The player can walk around with the left and right arrow keys and jump with the up arrow. Jumping is a specialty of this game character. It can reach several times its own height and is able to change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the onscreen avatar. ++The player can walk around with the left and right arrow keys, and jump with the up arrow. Jumping is a specialty of this game character. It can reach several times its own height and is able to change direction in midair. This may not be entirely realistic, but it helps give the player the feeling of being in direct control of the onscreen avatar. + +-The game consists of a fixed background, laid out like a grid, with the moving elements overlaid on that background. Each field on the grid is either empty, solid, or lava. The moving elements are the player, coins, and certain pieces of lava. Unlike the artificial life simulation from [Chapter 7](07_elife.html#elife), the positions of these elements are not constrained to the grid—their coordinates may be fractional, allowing smooth motion. ++The game consists of a fixed background, laid out like a grid, with the moving elements overlaid on that background. Each field on the grid is either empty, solid, or lava. The moving elements are the player, coins, and certain pieces of lava. The positions of these elements are not constrained to the grid—their coordinates may be fractional, allowing smooth motion. + + ## The technology + + We will use the browser DOM to display the game, and we'll read user input by handling key events. + +-The screen- and keyboard-related code is only a tiny part of the work we need to do to build this game. Since everything looks like colored boxes, drawing is uncomplicated: we create DOM elements and use styling to give them a background color, size, and position. ++The screen- and keyboard-related code is only a small part of the work we need to do to build this game. Since everything looks like colored boxes, drawing is uncomplicated: we create DOM elements and use styling to give them a background color, size, and position. + +-We can represent the background as a table since it is an unchanging grid of squares. The free-moving elements can be overlaid on top of that, using absolutely positioned elements. ++We can represent the background as a table since it is an unchanging grid of squares. The free-moving elements can be overlaid using absolutely positioned elements. + +-In games and other programs that have to animate graphics and respond to user input without noticeable delay, efficiency is important. Although the DOM was not originally designed for high-performance graphics, it is actually better at this than you would expect. You saw some animations in [Chapter 13](13_dom.html#animation). On a modern machine, a simple game like this performs well, even if we don't think about optimization much. ++In games and other programs that should animate graphics and respond to user input without noticeable delay, efficiency is important. Although the DOM was not originally designed for high-performance graphics, it is actually better at this than you would expect. You saw some animations in [Chapter 14](14_dom.html#animation). On a modern machine, a simple game like this performs well, even if we don't worry about optimization very much. + +-In the [next chapter](16_canvas.html#canvas), we will explore another browser technology, the `<canvas>` tag, which provides a more traditional way to draw graphics, working in terms of shapes and pixels rather than DOM elements. ++In the [next chapter](17_canvas.html), we will explore another browser technology, the `<canvas>` tag, which provides a more traditional way to draw graphics, working in terms of shapes and pixels rather than DOM elements. + + ## Levels + +-In [Chapter 7](07_elife.html#plan) we used arrays of strings to describe a two-dimensional grid. We can do the same here. It will allow us to design levels without first building a level editor. ++We'll want a human-readable, human-editable way to specify levels. Since it is okay for everything to start out on a grid, we could use big strings in which each character represents an element—either a part of the background grid or a moving element. + +-A simple level would look like this: ++The plan for a small level might look like this: + + ``` +-var simpleLevelPlan = [ +- " ", +- " ", +- " x = x ", +- " x o o x ", +- " x @ xxxxx x ", +- " xxxxx x ", +- " x!!!!!!!!!!!!x ", +- " xxxxxxxxxxxxxx ", +- " " +-]; ++var simpleLevelPlan = ` ++...................... ++..#................#.. ++..#..............=.#.. ++..#.........o.o....#.. ++..#.@......#####...#.. ++..#####............#.. ++......#++++++++++++#.. ++......##############.. ++......................`; + ``` + +-Both the fixed grid and the moving elements are included in the plan. The `x` characters stand for walls, the space characters for empty space, and the exclamation marks represent fixed, nonmoving lava tiles. ++Periods are empty space, hash ("#") characters are walls, and plus signs are lava. The player's starting position is the at sign (`@`). Every O characters is a coin, and the equals sign (`=`) at the top is a block of lava that moves back and forth horizontally. + +-The `@` defines the place where the player starts. Every `o` is a coin, and the equal sign (`=`) stands for a block of lava that moves back and forth horizontally. Note that the grid for these positions will be set to contain empty space, and another data structure is used to track the position of such moving elements. +- +-We'll support two other kinds of moving lava: the pipe character (`|`) for vertically moving blobs, and `v` for _dripping_ lava—vertically moving lava that doesn't bounce back and forth but only moves down, jumping back to its start position when it hits the floor. ++We'll support two additional kinds of moving lava: the pipe character (`|`) creates vertically moving blobs, and `v` indicates _dripping_ lava—vertically moving lava that doesn't bounce back and forth but only moves down, jumping back to its start position when it hits the floor. + + A whole game consists of multiple levels that the player must complete. A level is completed when all coins have been collected. If the player touches lava, the current level is restored to its starting position, and the player may try again. + + ## Reading a level + +-The following constructor builds a level object. Its argument should be the array of strings that define the level. +- +-``` +-function Level(plan) { +- this.width = plan[0].length; +- this.height = plan.length; +- this.grid = []; +- this.actors = []; +- +- for (var y = 0; y < this.height; y++) { +- var line = plan[y], gridLine = []; +- for (var x = 0; x < this.width; x++) { +- var ch = line[x], fieldType = null; +- var Actor = actorChars[ch]; +- if (Actor) +- this.actors.push(new Actor(new Vector(x, y), ch)); +- else if (ch == "x") +- fieldType = "wall"; +- else if (ch == "!") +- fieldType = "lava"; +- gridLine.push(fieldType); +- } +- this.grid.push(gridLine); +- } ++The following class stores a level object. Its argument should be the string that defines the level. ++ ++``` ++class Level { ++ constructor(plan) { ++ let rows = plan.trim().split("\n").map(l => [...l]); ++ this.height = rows.length; ++ this.width = rows[0].length; ++ this.startActors = []; + +- this.player = this.actors.filter(function(actor) { +- return actor.type == "player"; +- })[0]; +- this.status = this.finishDelay = null; ++ this.rows = rows.map((row, y) => { ++ return row.map((ch, x) => { ++ let type = levelChars[ch]; ++ if (typeof type == "string") return type; ++ this.startActors.push( ++ type.create(new Vec(x, y), ch)); ++ return "empty"; ++ }); ++ }); ++ } + } + ``` + +-For brevity, the code does not check for malformed input. It assumes that you've given it a proper level plan, complete with a player start position and other essentials. ++The `trim` method is used to remove whitespace at the start and end of the plan string. This allows our example plan to start with a newline, so that all the lines are directly below each other. The remaining string is split on newline characters, and each line is spread into an array, producing arrays of characters. ++ ++So `rows` holds an array of arrays of characters, the rows of the plan. We can derive the level's width and height from these. But we must still separate the moving elements from the background grid. We'll call moving elements _actors_. They'll be stored in an array of objects. The background will be an array of arrays of strings, holding field types like `"empty"`, `"wall"`, or `"lava"`. ++ ++To create these arrays we map over the rows, and then over their content. Remember that `map` passes the array index as a second argument to the mapping function, which tells us the the x- and y-coordinates of a given character. Positions in the game will be stored as pairs of coordinates, with the top left being 0,0, and each background square being 1 unit high and wide. + +-A level stores its width and height, along with two arrays—one for the grid and one for the _actors_, which are the dynamic elements. The grid is represented as an array of arrays, where each of the inner arrays represents a horizontal line and each square contains either null, for empty squares, or a string indicating the type of the square—`"wall"` or `"lava"`. ++To interpret the characters in the plan, the `Level` constructor uses the `levelChars` object, which maps background elements to strings and actor characters to classes. When `type` is an actor class, its static `create` method is used to create an object, which is added to `startActors`, and the mapping function returns `"empty"` for this background square. + +-The actors array holds objects that track the current position and state of the dynamic elements in the level. Each of these is expected to have a `pos` property that gives its position (the coordinates of its top-left corner), a `size` property that gives its size, and a `type` property that holds a string identifying the element (`"lava"`, `"coin"`, or `"player"`). ++The position of the actor is stored as a `Vec` object, which is a two-dimensional vector, an object with `x` and `y` properties, as seen in the exercises of [Chapter 6](06_object.html#exercise_vector). + +-After building the grid, we use the `filter` method to find the player actor object, which we store in a property of the level. The `status` property tracks whether the player has won or lost. When this happens, `finishDelay` is used to keep the level active for a short period of time so that a simple animation can be shown. (Immediately resetting or advancing the level would look cheap.) This method can be used to find out whether a level is finished: ++As the game runs, actors will end up in different places or even disappear entirely (as coins do when collected). We'll use a `State` class to track the state of a running game. + + ``` +-Level.prototype.isFinished = function() { +- return this.status != null && this.finishDelay < 0; +-}; ++class State { ++ constructor(level, actors, status) { ++ this.level = level; ++ this.actors = actors; ++ this.status = status; ++ } ++ ++ static start(level) { ++ return new State(level, level.startActors, "playing"); ++ } ++ ++ get player() { ++ return this.actors.find(a => a.type == "player"); ++ } ++} + ``` + ++The `status` property will switch to `"lost"` or `"won"` when the game has ended. ++ ++This is again a persistent data structure—updating the game state creates a new state, and leaves the old one intact. ++ + ## Actors + +-To store the position and size of an actor, we will return to our trusty `Vector` type, which groups an x-coordinate and a y-coordinate into an object. ++Actor objects represent the current position and state of a given moving element in our game. All actor objects conform to the same interface. Their `pos` property holds the coordinates of the element's top-left corner, and their `size` property holds its size. + +-``` +-function Vector(x, y) { +- this.x = x; this.y = y; +-} +-Vector.prototype.plus = function(other) { +- return new Vector(this.x + other.x, this.y + other.y); +-}; +-Vector.prototype.times = function(factor) { +- return new Vector(this.x * factor, this.y * factor); +-}; +-``` ++Then they have an `update` method, which is used to compute their new state and position after a given time step. It simulates the thing the actor does—moving in response to the arrow keys for the player, bouncing back and forth for the lava—and returns a new, updated actor object. + +-The `times` method scales a vector by a given amount. It will be useful when we need to multiply a speed vector by a time interval to get the distance traveled during that time. ++A `type` property contains a string that identifies the type of the actor—`"player"`, `"coin"`, or `"lava"`. This is useful when drawing the game—the look of the rectangle drawn for an actor is based on its type. + +-In the previous section, the `actorChars` object was used by the `Level` constructor to associate characters with constructor functions. The object looks like this: ++Actor classes have a static `create` method that is used by the `Level` constructor to create an actor from a character in the level plan. It is given the coordinates of the character and the character itself, which is needed because the `Lava` class handles several different characters. ++ ++This is the `Vec` class that we'll use for our two-dimensional values, such as the position and size of actors. + + ``` +-var actorChars = { +- "@": Player, +- "o": Coin, +- "=": Lava, "|": Lava, "v": Lava +-}; ++class Vec { ++ constructor(x, y) { ++ this.x = x; this.y = y; ++ } ++ plus(other) { ++ return new Vec(this.x + other.x, this.y + other.y); ++ } ++ times(factor) { ++ return new Vec(this.x * factor, this.y * factor); ++ } ++} + ``` + +-Three characters map to `Lava`. The `Level` constructor passes the actor's source character as the second argument to the constructor, and the `Lava` constructor uses that to adjust its behavior (bouncing horizontally, bouncing vertically, or dripping). ++The `times` method scales a vector by a given number. It will be useful when we need to multiply a speed vector by a time interval to get the distance traveled during that time. ++ ++The different types of actors get their own classes, since their behavior is very different. Let's define these classes. We'll get to their `update` methods later on. + +-The player type is built with the following constructor. It has a property `speed` that stores its current speed, which will help simulate momentum and gravity. ++The player class has a property `speed` that stores its current speed, to simulate momentum and gravity. + + ``` +-function Player(pos) { +- this.pos = pos.plus(new Vector(0, -0.5)); +- this.size = new Vector(0.8, 1.5); +- this.speed = new Vector(0, 0); ++class Player { ++ constructor(pos, speed) { ++ this.pos = pos; ++ this.speed = speed; ++ } ++ ++ get type() { return "player"; } ++ ++ static create(pos) { ++ return new Player(pos.plus(new Vec(0, -0.5)), ++ new Vec(0, 0)); ++ } + } +-Player.prototype.type = "player"; ++ ++Player.prototype.size = new Vec(0.8, 1.5); + ``` + + Because a player is one-and-a-half squares high, its initial position is set to be half a square above the position where the `@` character appeared. This way, its bottom aligns with the bottom of the square it appeared in. + +-When constructing a dynamic `Lava` object, we need to initialize the object differently depending on the character it is based on. Dynamic lava moves along at its given speed until it hits an obstacle. At that point, if it has a `repeatPos` property, it will jump back to its start position (dripping). If it does not, it will invert its speed and continue in the other direction (bouncing). The constructor only sets up the necessary properties. The method that does the actual moving will be written [later](15_game.html#actors). ++The `size` property is the same for all instances of `Player`, so we store it on the prototype, rather than on the instances themselves. We could have used a getter like `type`, but that would create and return a new `Vec` object every time the property is read, which would be wasteful. (Strings, being immutable, don't have to be recreated every time they are evaluated.) ++ ++When constructing a `Lava` actor, we need to initialize the object differently depending on the character it is based on. Dynamic lava moves along at its current speed until it hits an obstacle. At that point, if it has a `reset` property, it will jump back to its start position (dripping). If it does not, it will invert its speed and continue in the other direction (bouncing). ++ ++The `create` method looks at the character that the `Level` constructor passes, and creates the appropriate lava actor. + + ``` +-function Lava(pos, ch) { +- this.pos = pos; +- this.size = new Vector(1, 1); +- if (ch == "=") { +- this.speed = new Vector(2, 0); +- } else if (ch == "|") { +- this.speed = new Vector(0, 2); +- } else if (ch == "v") { +- this.speed = new Vector(0, 3); +- this.repeatPos = pos; ++class Lava { ++ constructor(pos, speed, reset) { ++ this.pos = pos; ++ this.speed = speed; ++ this.reset = reset; ++ } ++ ++ get type() { return "lava"; } ++ ++ static create(pos, ch) { ++ if (ch == "=") { ++ return new Lava(pos, new Vec(2, 0)); ++ } else if (ch == "|") { ++ return new Lava(pos, new Vec(0, 2)); ++ } else if (ch == "v") { ++ return new Lava(pos, new Vec(0, 3), pos); ++ } + } + } +-Lava.prototype.type = "lava"; ++ ++Lava.prototype.size = new Vec(1, 1); + ``` + +-`Coin` actors are simple. They mostly just sit in their place. But to liven up the game a little, they are given a “wobble”, a slight vertical motion back and forth. To track this, a coin object stores a base position as well as a `wobble` property that tracks the phase of the bouncing motion. Together, these determine the coin's actual position (stored in the `pos` property). ++`Coin` actors are relatively simple. They mostly just sit in their place. But to liven up the game a little, they are given a “wobble”, a slight vertical back and forth motion. To track this, a coin object stores a base position as well as a `wobble` property that tracks the phase of the bouncing motion. Together, these determine the coin's actual position (stored in the `pos` property). + + ``` +-function Coin(pos) { +- this.basePos = this.pos = pos.plus(new Vector(0.2, 0.1)); +- this.size = new Vector(0.6, 0.6); +- this.wobble = Math.random() * Math.PI * 2; ++class Coin { ++ constructor(pos, basePos, wobble) { ++ this.pos = pos; ++ this.basePos = basePos; ++ this.wobble = wobble; ++ } ++ ++ get type() { return "coin"; } ++ ++ static create(pos) { ++ let basePos = pos.plus(new Vec(0.2, 0.1)); ++ return new Coin(basePos, basePos, ++ Math.random() * Math.PI * 2); ++ } + } +-Coin.prototype.type = "coin"; ++ ++Coin.prototype.size = new Vec(0.6, 0.6); + ``` + +-In [Chapter 13](13_dom.html#sin_cos), we saw that `Math.sin` gives us the y-coordinate of a point on a circle. That coordinate goes back and forth in a smooth wave form as we move along the circle, which makes the sine function useful for modeling a wavy motion. ++In [Chapter 14](14_dom.html#sin_cos), we saw that `Math.sin` gives us the y-coordinate of a point on a circle. That coordinate goes back and forth in a smooth wave form as we move along the circle, which makes the sine function useful for modeling a wavy motion. + + To avoid a situation where all coins move up and down synchronously, the starting phase of each coin is randomized. The _phase_ of `Math.sin`'s wave, the width of a wave it produces, is 2π. We multiply the value returned by `Math.random` by that number to give the coin a random starting position on the wave. + +-We have now written all the parts needed to represent the state of a level. ++We can now define the `levelChars` object that maps plan characters to either background grid types or actor classes. + + ``` +-var simpleLevel = new Level(simpleLevelPlan); +-console.log(simpleLevel.width, "by", simpleLevel.height); ++const levelChars = { ++ ".": "empty", "#": "wall", "+": "lava", ++ "@": Player, "o": Coin, ++ "=": Lava, "|": Lava, "v": Lava ++}; ++``` ++ ++That gives us all the parts needed to create a `Level` instance. ++ ++``` ++let simpleLevel = new Level(simpleLevelPlan); ++console.log(`${simpleLevel.width} by ${simpleLevel.height}`); + // → 22 by 9 + ``` + +@@ -198,67 +248,70 @@ The task ahead is to display such levels on the screen and to model time and mot + + ## Encapsulation as a burden + +-Most of the code in this chapter does not worry about encapsulation for two reasons. First, encapsulation takes extra effort. It makes programs bigger and requires additional concepts and interfaces to be introduced. Since there is only so much code you can throw at a reader before their eyes glaze over, I've made an effort to keep the program small. ++Most of the code in this chapter does not worry about encapsulation very much, for two reasons. First, encapsulation takes extra effort. It makes programs bigger and requires additional concepts and interfaces to be introduced. Since there is only so much code you can throw at a reader before their eyes glaze over, I've made an effort to keep the program small. + + Second, the various elements in this game are so closely tied together that if the behavior of one of them changed, it is unlikely that any of the others would be able to stay the same. Interfaces between the elements would end up encoding a lot of assumptions about the way the game works. This makes them a lot less effective—whenever you change one part of the system, you still have to worry about the way it impacts the other parts because their interfaces wouldn't cover the new situation. + +-Some _cutting points_ in a system lend themselves well to separation through rigorous interfaces, but others don't. Trying to encapsulate something that isn't a suitable boundary is a sure way to waste a lot of energy. When you are making this mistake, you'll usually notice that your interfaces are getting awkwardly large and detailed and that they need to be modified often, as the program evolves. ++Some _cutting points_ in a system lend themselves well to separation through rigorous interfaces, but others don't. Trying to encapsulate something that isn't a suitable boundary is a sure way to waste a lot of energy. When you are making this mistake, you'll usually notice that your interfaces are getting awkwardly large and detailed and that they need to be changed often, as the program evolves. + +-There is one thing that we _will_ encapsulate in this chapter, and that is the drawing subsystem. The reason for this is that we will display the same game in a different way in the [next chapter](16_canvas.html#canvasdisplay). By putting the drawing behind an interface, we can simply load the same game program there and plug in a new display module. ++There is one thing that we _will_ encapsulate, and that is the drawing subsystem. The reason for this is that we'll display the same game in a different way in the [next chapter](17_canvas.html#canvasdisplay). By putting the drawing behind an interface, we can load the same game program there and plug in a new display module. + + ## Drawing + +-The encapsulation of the drawing code is done by defining a _display_ object, which displays a given level. The display type we define in this chapter is called `DOMDisplay` because it uses simple DOM elements to show the level. ++The encapsulation of the drawing code is done by defining a _display_ object, which displays a given level and state. The display type we define in this chapter is called `DOMDisplay` because it uses DOM elements to show the level. + +-We will be using a style sheet to set the actual colors and other fixed properties of the elements that make up the game. It would also be possible to directly assign to the elements' `style` property when we create them, but that would produce more verbose programs. ++We'll be using a style sheet to set the actual colors and other fixed properties of the elements that make up the game. It would also be possible to directly assign to the elements' `style` property when we create them, but that would produce more verbose programs. + +-The following helper function provides a short way to create an element and give it a class: ++The following helper function provides a succinct way to create an element and give it some attributes and child nodes: + + ``` +-function elt(name, className) { +- var elt = document.createElement(name); +- if (className) elt.className = className; +- return elt; ++function elt(name, attrs, ...children) { ++ let dom = document.createElement(name); ++ for (let attr of Object.keys(attrs)) { ++ dom.setAttribute(attr, attrs[attr]); ++ } ++ for (let child of children) { ++ dom.appendChild(child); ++ } ++ return dom; + } + ``` + + A display is created by giving it a parent element to which it should append itself and a level object. + + ``` +-function DOMDisplay(parent, level) { +- this.wrap = parent.appendChild(elt("div", "game")); +- this.level = level; ++class DOMDisplay { ++ constructor(parent, level) { ++ this.dom = elt("div", {class: "game"}, drawGrid(level)); ++ this.actorLayer = null; ++ parent.appendChild(this.dom); ++ } + +- this.wrap.appendChild(this.drawBackground()); +- this.actorLayer = null; +- this.drawFrame(); ++ clear() { this.dom.remove(); } + } + ``` + +-We used the fact that `appendChild` returns the appended element to create the wrapper element and store it in the `wrap` property in a single statement. +- +-The level's background, which never changes, is drawn once. The actors are redrawn every time the display is updated. The `actorLayer` property will be used by `drawFrame` to track the element that holds the actors so that they can be easily removed and replaced. ++The level's background grid, which never changes, is drawn once. Actors are redrawn every time the display is updated with a given state. The `actorLayer` property will be used to track the element that holds the actors so that they can be easily removed and replaced. + +-Our coordinates and sizes are tracked in units relative to the grid size, where a size or distance of 1 means 1 grid unit. When setting pixel sizes, we will have to scale these coordinates up—everything in the game would be ridiculously small at a single pixel per square. The `scale` variable gives the number of pixels that a single unit takes up on the screen. ++Our coordinates and sizes are tracked in grid units, where a size or distance of 1 means 1 grid block. When setting pixel sizes, we will have to scale these coordinates up—everything in the game would be ridiculously small at a single pixel per square. The `scale` constant gives the number of pixels that a single unit takes up on the screen. + + ``` +-var scale = 20; ++const scale = 20; + +-DOMDisplay.prototype.drawBackground = function() { +- var table = elt("table", "background"); +- table.style.width = this.level.width * scale + "px"; +- this.level.grid.forEach(function(row) { +- var rowElt = table.appendChild(elt("tr")); +- rowElt.style.height = scale + "px"; +- row.forEach(function(type) { +- rowElt.appendChild(elt("td", type)); +- }); +- }); +- return table; +-}; ++function drawGrid(level) { ++ return elt("table", { ++ class: "background", ++ style: `width: ${level.width * scale}px` ++ }, ...level.rows.map(row => ++ elt("tr", {style: `height: ${scale}px`}, ++ ...row.map(type => elt("td", {class: type}))) ++ )); ++} + ``` + +-As mentioned earlier, the background is drawn as a `<table>` element. This nicely corresponds to the structure of the `grid` property in the level—each row of the grid is turned into a table row (`<tr>` element). The strings in the grid are used as class names for the table cell (`<td>`) elements. The following CSS helps the resulting table look like the background we want: ++As mentioned before, the background is drawn as a `<table>` element. This nicely corresponds to the structure of the `rows` property of the level—each row of the grid is turned into a table row (`<tr>` element). The strings in the grid are used as class names for the table cell (`<td>`) elements. The spread (triple dot) operator is used to pass arrays of child nodes to `elt` as separate arguments. ++ ++The following CSS makes the table look like the background we want: + + ``` + .background { background: rgb(52, 166, 251); +@@ -269,25 +322,23 @@ As mentioned earlier, the background is drawn as a `<table>` element. This + .wall { background: white; } + ``` + +-Some of these (`table-layout`, `border-spacing`, and `padding`) are simply used to suppress unwanted default behavior. We don't want the layout of the table to depend upon the contents of its cells, and we don't want space between the table cells or padding inside them. ++Some of these (`table-layout`, `border-spacing`, and `padding`) are used to suppress unwanted default behavior. We don't want the layout of the table to depend upon the contents of its cells, and we don't want space between the table cells or padding inside them. + +-The `background` rule sets the background color. CSS allows colors to be specified both as words (`white`) and with a format such as `rgb(R, G, B)`, where the red, green, and blue components of the color are separated into three numbers from 0 to 255\. So, in `rgb(52, 166, 251)`, the red component is 52, green is 166, and blue is 251\. Since the blue component is the largest, the resulting color will be bluish. You can see that in the `.lava` rule, the first number (red) is the largest. ++The `background` rule sets the background color. CSS allows colors to be specified both as words (`white`) but also with a format such as `rgb(R, G, B)`, where the red, green, and blue components of the color are separated into three numbers from 0 to 255\. So, in `rgb(52, 166, 251)`, the red component is 52, green is 166, and blue is 251\. Since the blue component is the largest, the resulting color will be bluish. You can see that in the `.lava` rule, the first number (red) is the largest. + + We draw each actor by creating a DOM element for it and setting that element's position and size based on the actor's properties. The values have to be multiplied by `scale` to go from game units to pixels. + + ``` +-DOMDisplay.prototype.drawActors = function() { +- var wrap = elt("div"); +- this.level.actors.forEach(function(actor) { +- var rect = wrap.appendChild(elt("div", +- "actor " + actor.type)); +- rect.style.width = actor.size.x * scale + "px"; +- rect.style.height = actor.size.y * scale + "px"; +- rect.style.left = actor.pos.x * scale + "px"; +- rect.style.top = actor.pos.y * scale + "px"; +- }); +- return wrap; +-}; ++function drawActors(actors) { ++ return elt("div", {}, ...actors.map(actor => { ++ let rect = elt("div", {class: `actor ${actor.type}`}); ++ rect.style.width = `${actor.size.x * scale}px`; ++ rect.style.height = `${actor.size.y * scale}px`; ++ rect.style.left = `${actor.pos.x * scale}px`; ++ rect.style.top = `${actor.pos.y * scale}px`; ++ return rect; ++ })); ++} + ``` + + To give an element more than one class, we separate the class names by spaces. In the CSS code shown next, the `actor` class gives the actors their absolute position. Their type name is used as an extra class to give them a color. We don't have to define the `lava` class again because we reuse the class for the lava grid squares which we defined earlier. +@@ -298,15 +349,15 @@ To give an element more than one class, we separate the class names by spaces. I + .player { background: rgb(64, 64, 64); } + ``` + +-When it updates the display, the `drawFrame` method first removes the old actor graphics, if any, and then redraws them in their new positions. It may be tempting to try to reuse the DOM elements for actors, but to make that work, we would need a lot of additional information flow between the display code and the simulation code. We'd need to associate actors with DOM elements, and the drawing code must remove elements when their actors vanish. Since there will typically be only a handful of actors in the game, redrawing all of them is not expensive. ++The `setState` method is used to make the display show a given state. It first removes the old actor graphics, if any, and then redraws the actors in their new positions. It may be tempting to try to reuse the DOM elements for actors, but to make that work, we would need a lot of additional bookkeeping to associate actors with DOM elements and to make sure we remove elements when their actors vanish. Since there will typically be only a handful of actors in the game, redrawing all of them is not expensive. + + ``` +-DOMDisplay.prototype.drawFrame = function() { +- if (this.actorLayer) +- this.wrap.removeChild(this.actorLayer); +- this.actorLayer = this.wrap.appendChild(this.drawActors()); +- this.wrap.className = "game " + (this.level.status || ""); +- this.scrollPlayerIntoView(); ++DOMDisplay.prototype.setState = function(state) { ++ if (this.actorLayer) this.actorLayer.remove(); ++ this.actorLayer = drawActors(state.actors); ++ this.dom.appendChild(this.actorLayer); ++ this.dom.className = `game ${state.status}`; ++ this.scrollPlayerIntoView(state); + }; + ``` + +@@ -321,9 +372,9 @@ By adding the level's current status as a class name to the wrapper, we can styl + } + ``` + +-After touching lava, the player's color turns dark red, suggesting scorching. When the last coin has been collected, we use two blurred white box shadows, one to the top left and one to the top right, to create a white halo effect. ++After touching lava, the player's color turns dark red, suggesting scorching. When the last coin has been collected, we add two blurred white shadows—one to the top left and one to the top right—to create a white halo effect. + +-We can't assume that levels always fit in the viewport. That is why the `scrollPlayerIntoView` call is needed—it ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following CSS gives the game's wrapping DOM element a maximum size and ensures that anything that sticks out of the element's box is not visible. We also give the outer element a relative position so that the actors inside it are positioned relative to the level's top-left corner. ++We can't assume that the level always fits in the _viewport_—the element into which we draw the game. That is why the `scrollPlayerIntoView` call is needed—it ensures that if the level is protruding outside the viewport, we scroll that viewport to make sure the player is near its center. The following CSS gives the game's wrapping DOM element a maximum size and ensures that anything that sticks out of the element's box is not visible. We also give the outer element a relative position so that the actors inside it are positioned relative to the level's top-left corner. + + ``` + .game { +@@ -337,52 +388,47 @@ We can't assume that levels always fit in the viewport. That is why the `scrollP + In the `scrollPlayerIntoView` method, we find the player's position and update the wrapping element's scroll position. We change the scroll position by manipulating that element's `scrollLeft` and `scrollTop` properties when the player is too close to the edge. + + ``` +-DOMDisplay.prototype.scrollPlayerIntoView = function() { +- var width = this.wrap.clientWidth; +- var height = this.wrap.clientHeight; +- var margin = width / 3; ++DOMDisplay.prototype.scrollPlayerIntoView = function(state) { ++ let width = this.dom.clientWidth; ++ let height = this.dom.clientHeight; ++ let margin = width / 3; + + // The viewport +- var left = this.wrap.scrollLeft, right = left + width; +- var top = this.wrap.scrollTop, bottom = top + height; +- +- var player = this.level.player; +- var center = player.pos.plus(player.size.times(0.5)) +- .times(scale); +- +- if (center.x < left + margin) +- this.wrap.scrollLeft = center.x - margin; +- else if (center.x > right - margin) +- this.wrap.scrollLeft = center.x + margin - width; +- if (center.y < top + margin) +- this.wrap.scrollTop = center.y - margin; +- else if (center.y > bottom - margin) +- this.wrap.scrollTop = center.y + margin - height; ++ let left = this.dom.scrollLeft, right = left + width; ++ let top = this.dom.scrollTop, bottom = top + height; ++ ++ let player = state.player; ++ let center = player.pos.plus(player.size.times(0.5)) ++ .times(scale); ++ ++ if (center.x < left + margin) { ++ this.dom.scrollLeft = center.x - margin; ++ } else if (center.x > right - margin) { ++ this.dom.scrollLeft = center.x + margin - width; ++ } ++ if (center.y < top + margin) { ++ this.dom.scrollTop = center.y - margin; ++ } else if (center.y > bottom - margin) { ++ this.dom.scrollTop = center.y + margin - height; ++ } + }; + ``` + +-The way the player's center is found shows how the methods on our `Vector` type allow computations with objects to be written in a readable way. To find the actor's center, we add its position (its top-left corner) and half its size. That is the center in level coordinates, but we need it in pixel coordinates, so we then multiply the resulting vector by our display scale. ++The way the player's center is found shows how the methods on our `Vec` type allow computations with objects to be written in a relatively readable way. To find the actor's center, we add its position (its top-left corner) and half its size. That is the center in level coordinates, but we need it in pixel coordinates, so we then multiply the resulting vector by our display scale. + +-Next, a series of checks verify that the player position isn't outside of the allowed range. Note that sometimes this will set nonsense scroll coordinates, below zero or beyond the element's scrollable area. This is okay—the DOM will constrain them to sane values. Setting `scrollLeft` to -10 will cause it to become 0. ++Next, a series of checks verify that the player position isn't outside of the allowed range. Note that sometimes this will set nonsense scroll coordinates, below zero or beyond the element's scrollable area. This is okay—the DOM will constrain them to acceptable values. Setting `scrollLeft` to -10 will cause it to become 0. + + It would have been slightly simpler to always try to scroll the player to the center of the viewport. But this creates a rather jarring effect. As you are jumping, the view will constantly shift up and down. It is more pleasant to have a “neutral” area in the middle of the screen where you can move around without causing any scrolling. + +-Finally, we'll need a way to clear a displayed level, to be used when the game moves to the next level or resets a level. +- +-``` +-DOMDisplay.prototype.clear = function() { +- this.wrap.parentNode.removeChild(this.wrap); +-}; +-``` +- + We are now able to display our tiny level. + + ``` + + + + ``` + +@@ -390,312 +436,264 @@ The `<link>` tag, when used with `rel="stylesheet"`, is a way to load a CS + + ## Motion and collision + +-Now we're at the point where we can start adding motion—the most interesting aspect of the game. The basic approach, taken by most games like this, is to split time into small steps and, for each step, move the actors by a distance corresponding to their speed (distance moved per second) multiplied by the size of the time step (in seconds). ++Now we're at the point where we can start adding motion—the most interesting aspect of the game. The basic approach, taken by most games like this, is to split time into small steps and, for each step, move the actors by a distance corresponding to their speed multiplied by the size of the time step. We'll measure time in seconds, so speeds are expressed in units per second. + +-That is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. For coins, the coin must be collected, and so on. ++Moving things is easy. The difficult part is dealing with the interactions between the elements. When the player hits a wall or floor, they should not simply move through it. The game must notice when a given motion causes an object to hit another object and respond accordingly. For walls, the motion must be stopped. When hitting a coin, it must be collected. When touching lava, the game should be lost. + + Solving this for the general case is a big task. You can find libraries, usually called _physics engines_, that simulate interaction between physical objects in two or three dimensions. We'll take a more modest approach in this chapter, handling only collisions between rectangular objects and handling them in a rather simplistic way. + +-Before moving the player or a block of lava, we test whether the motion would take it inside of a nonempty part of the background. If it does, we simply cancel the motion altogether. The response to such a collision depends on the type of actor—the player will stop, whereas a lava block will bounce back. ++Before moving the player or a block of lava, we test whether the motion would take it inside of a wall. If it does, we simply cancel the motion altogether. The response to such a collision depends on the type of actor—the player will stop, whereas a lava block will bounce back. + + This approach requires our time steps to be rather small since it will cause motion to stop before the objects actually touch. If the time steps (and thus the motion steps) are too big, the player would end up hovering a noticeable distance above the ground. Another approach, arguably better but more complicated, would be to find the exact collision spot and move there. We will take the simple approach and hide its problems by ensuring the animation proceeds in small steps. + +-This method tells us whether a rectangle (specified by a position and a size) overlaps with any nonempty space on the background grid: ++This method tells us whether a rectangle (specified by a position and a size) touches a grid element of the given type. + + ``` +-Level.prototype.obstacleAt = function(pos, size) { ++Level.prototype.touches = function(pos, size, type) { + var xStart = Math.floor(pos.x); + var xEnd = Math.ceil(pos.x + size.x); + var yStart = Math.floor(pos.y); + var yEnd = Math.ceil(pos.y + size.y); + +- if (xStart < 0 || xEnd > this.width || yStart < 0) +- return "wall"; +- if (yEnd > this.height) +- return "lava"; + for (var y = yStart; y < yEnd; y++) { + for (var x = xStart; x < xEnd; x++) { +- var fieldType = this.grid[y][x]; +- if (fieldType) return fieldType; ++ let isOutside = x < 0 || x >= this.width || ++ y < 0 || y >= this.height; ++ let here = isOutside ? "wall" : this.rows[y][x]; ++ if (here == type) return true; + } + } ++ return false; + }; + ``` + +-This method computes the set of grid squares that the body overlaps with by using `Math.floor` and `Math.ceil` on the body's coordinates. Remember that grid squares are 1×1 units in size. By rounding the sides of a box up and down, we get the range of background squares that the box touches. +- +-![Finding collisions on a grid](img/game-grid.svg) ++The method computes the set of grid squares that the body overlaps with by using `Math.floor` and `Math.ceil` on its coordinates. Remember that grid squares are 1 by 1 units in size. By rounding the sides of a box up and down, we get the range of background squares that the box touches. + +-If the body sticks out of the level, we always return `"wall"` for the sides and top and `"lava"` for the bottom. This ensures that the player dies when falling out of the world. When the body is fully inside the grid, we loop over the block of grid squares found by rounding the coordinates and return the content of the first nonempty square we find. ++
![Finding collisions on a grid](img/game-grid.svg)
+ +-Collisions between the player and other dynamic actors (coins, moving lava) are handled _after_ the player moved. When the motion has taken the player into another actor, the appropriate effect—collecting a coin or dying—is activated. ++We loop over the block of grid squares found by rounding the coordinates and return `true` when a matching square is found. Squares outside of the level are always treated as `"wall"` to ensure that the player can't leave the world and that we won't accidentally try to read outside of the bounds of our `rows` array. + +-This method scans the array of actors, looking for an actor that overlaps the one given as an argument: ++The state `update` method uses `touches` to figure out if the player is touching lava. + + ``` +-Level.prototype.actorAt = function(actor) { +- for (var i = 0; i < this.actors.length; i++) { +- var other = this.actors[i]; +- if (other != actor && +- actor.pos.x + actor.size.x > other.pos.x && +- actor.pos.x < other.pos.x + other.size.x && +- actor.pos.y + actor.size.y > other.pos.y && +- actor.pos.y < other.pos.y + other.size.y) +- return other; +- } +-}; +-``` ++State.prototype.update = function(time, keys) { ++ let actors = this.actors ++ .map(actor => actor.update(time, this, keys)); ++ let newState = new State(this.level, actors, this.status); + +-## Actors and actions ++ if (newState.status != "playing") return newState; + +-The `animate` method on the `Level` type gives all actors in the level a chance to move. Its `step` argument is the time step in seconds. The `keys` object contains information about the arrow keys the player has pressed. +- +-``` +-var maxStep = 0.05; +- +-Level.prototype.animate = function(step, keys) { +- if (this.status != null) +- this.finishDelay -= step; ++ let player = newState.player; ++ if (this.level.touches(player.pos, player.size, "lava")) { ++ return new State(this.level, actors, "lost"); ++ } + +- while (step > 0) { +- var thisStep = Math.min(step, maxStep); +- this.actors.forEach(function(actor) { +- actor.act(thisStep, this, keys); +- }, this); +- step -= thisStep; ++ for (let actor of actors) { ++ if (actor != player && overlap(actor, player)) { ++ newState = actor.collide(newState); ++ } + } ++ return newState; + }; + ``` + +-When the level's `status` property has a non-null value (which is the case when the player has won or lost), we must count down the `finishDelay` property, which tracks the time between the point where winning or losing happens and the point where we want to stop showing the level. ++It is passed a time step and a data structure that tells it which keys are being held down. The first thing it does is call the `update` method on all actors, producing an array of updated actors. The actors also get the time step, the keys, and the state, so that they can base their update on those. Only the player will actually read keys, since that's the only actor that's controlled by the keyboard. + +-The `while` loop cuts the time step we are animating into suitably small pieces. It ensures that no step larger than `maxStep` is taken. For example, a `step` of 0.12 second would be cut into two steps of 0.05 seconds and one step of 0.02. ++If the game is already over, no further processing has to be done (the game can't be won after being lost, or vice-versa). Otherwise, the method tests whether the player is touching background lava. If so, the game is lost and we're done. Finally, if the game really is still going on, it sees if any other actors overlap the player. + +-Actor objects have an `act` method, which takes as arguments the time step, the level object, and the `keys` object. Here is one, for the `Lava` actor type, which ignores the `keys` object: ++Overlap between actors is detected with the `overlap` function. It takes two actor objects and returns true when they touch—which is the case when they overlap both along the x axis and along the y axis. + + ``` +-Lava.prototype.act = function(step, level) { +- var newPos = this.pos.plus(this.speed.times(step)); +- if (!level.obstacleAt(newPos, this.size)) +- this.pos = newPos; +- else if (this.repeatPos) +- this.pos = this.repeatPos; +- else +- this.speed = this.speed.times(-1); +-}; ++function overlap(actor1, actor2) { ++ return actor1.pos.x + actor1.size.x > actor2.pos.x && ++ actor1.pos.x < actor2.pos.x + actor2.size.x && ++ actor1.pos.y + actor1.size.y > actor2.pos.y && ++ actor1.pos.y < actor2.pos.y + actor2.size.y; ++} + ``` + +-It computes a new position by adding the product of the time step and its current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the lava block—dripping lava has a `repeatPos` property, to which it jumps back when it hits something. Bouncing lava simply inverts its speed (multiplies it by -1) in order to start moving in the other direction. +- +-Coins use their `act` method to wobble. They ignore collisions since they are simply wobbling around inside of their own square, and collisions with the player will be handled by the _player_'s `act` method. ++If any actor does overlap, its `collide` method gets a chance to update the state. Touching a lava actor sets the game status to `"lost"`, coins vanish when you touch them, and set the status to `"won"` when this was the last coin. + + ``` +-var wobbleSpeed = 8, wobbleDist = 0.07; ++Lava.prototype.collide = function(state) { ++ return new State(state.level, state.actors, "lost"); ++}; + +-Coin.prototype.act = function(step) { +- this.wobble += step * wobbleSpeed; +- var wobblePos = Math.sin(this.wobble) * wobbleDist; +- this.pos = this.basePos.plus(new Vector(0, wobblePos)); ++Coin.prototype.collide = function(state) { ++ let filtered = state.actors.filter(a => a != this); ++ let status = state.status; ++ if (!filtered.some(a => a.type == "coin")) status = "won"; ++ return new State(state.level, filtered, status); + }; + ``` + +-The `wobble` property is updated to track time and then used as an argument to `Math.sin` to create a wave, which is used to compute a new position. ++## Actor updates + +-That leaves the player itself. Player motion is handled separately per axis because hitting the floor should not prevent horizontal motion, and hitting a wall should not stop falling or jumping motion. This method implements the horizontal part: ++Actor objects' `update` methods take as arguments the time step, the state object, and a `keys` object. The one for the `Lava` actor type ignores the `keys` object. + + ``` +-var playerXSpeed = 7; +- +-Player.prototype.moveX = function(step, level, keys) { +- this.speed.x = 0; +- if (keys.left) this.speed.x -= playerXSpeed; +- if (keys.right) this.speed.x += playerXSpeed; +- +- var motion = new Vector(this.speed.x * step, 0); +- var newPos = this.pos.plus(motion); +- var obstacle = level.obstacleAt(newPos, this.size); +- if (obstacle) +- level.playerTouched(obstacle); +- else +- this.pos = newPos; ++Lava.prototype.update = function(time, state) { ++ let newPos = this.pos.plus(this.speed.times(time)); ++ if (!state.level.touches(newPos, this.size, "wall")) { ++ return new Lava(newPos, this.speed, this.reset); ++ } else if (this.reset) { ++ return new Lava(this.reset, this.speed, this.reset); ++ } else { ++ return new Lava(this.pos, this.speed.times(-1)); ++ } + }; + ``` + +-The horizontal motion is computed based on the state of the left and right arrow keys. When a motion causes the player to hit something, the level's `playerTouched` method, which handles things like dying in lava and collecting coins, is called. Otherwise, the object updates its position. ++It computes a new position by adding the product of the time step and the current speed to its old position. If no obstacle blocks that new position, it moves there. If there is an obstacle, the behavior depends on the type of the lava block—dripping lava has a `reset` position, to which it jumps back when it hits something. Bouncing lava inverts its speed by multiplying it by -1, so that it starts moving in the opposite direction. + +-Vertical motion works in a similar way but has to simulate jumping and gravity. ++Coins use their `act` method to wobble. They ignore collisions with the grid since they are simply wobbling around inside of their own square. + + ``` +-var gravity = 30; +-var jumpSpeed = 17; ++const wobbleSpeed = 8, wobbleDist = 0.07; + +-Player.prototype.moveY = function(step, level, keys) { +- this.speed.y += step * gravity; +- var motion = new Vector(0, this.speed.y * step); +- var newPos = this.pos.plus(motion); +- var obstacle = level.obstacleAt(newPos, this.size); +- if (obstacle) { +- level.playerTouched(obstacle); +- if (keys.up && this.speed.y > 0) +- this.speed.y = -jumpSpeed; +- else +- this.speed.y = 0; +- } else { +- this.pos = newPos; +- } ++Coin.prototype.update = function(time) { ++ let wobble = this.wobble + time * wobbleSpeed; ++ let wobblePos = Math.sin(wobble) * wobbleDist; ++ return new Coin(this.basePos.plus(new Vec(0, wobblePos)), ++ this.basePos, wobble); + }; + ``` + +-At the start of the method, the player is accelerated vertically to account for gravity. The gravity, jumping speed, and pretty much all other constants in this game have been set by trial and error. I tested various values until I found a combination I liked. +- +-Next, we check for obstacles again. If we hit an obstacle, there are two possible outcomes. When the up arrow is pressed _and_ we are moving down (meaning the thing we hit is below us), the speed is set to a relatively large, negative value. This causes the player to jump. If that is not the case, we simply bumped into something, and the speed is reset to zero. ++The `wobble` property is incremented to track time and then used as an argument to `Math.sin` to find the new position on the wave. The coin's current position is then computed from its base position and an offset based on this wave. + +-The actual `act` method looks like this: ++That leaves the player itself. Player motion is handled separately per axis because hitting the floor should not prevent horizontal motion, and hitting a wall should not stop falling or jumping motion. + + ``` +-Player.prototype.act = function(step, level, keys) { +- this.moveX(step, level, keys); +- this.moveY(step, level, keys); ++const playerXSpeed = 7; ++const gravity = 30; ++const jumpSpeed = 17; + +- var otherActor = level.actorAt(this); +- if (otherActor) +- level.playerTouched(otherActor.type, otherActor); ++Player.prototype.update = function(time, state, keys) { ++ let xSpeed = 0; ++ if (keys.ArrowLeft) xSpeed -= playerXSpeed; ++ if (keys.ArrowRight) xSpeed += playerXSpeed; ++ let pos = this.pos; ++ let movedX = pos.plus(new Vec(xSpeed * time, 0)); ++ if (!state.level.touches(movedX, this.size, "wall")) { ++ pos = movedX; ++ } + +- // Losing animation +- if (level.status == "lost") { +- this.pos.y += step; +- this.size.y -= step; ++ let ySpeed = this.speed.y + time * gravity; ++ let movedY = pos.plus(new Vec(0, ySpeed * time)); ++ if (!state.level.touches(movedY, this.size, "wall")) { ++ pos = movedY; ++ } else if (keys.ArrowUp && ySpeed > 0) { ++ ySpeed = -jumpSpeed; ++ } else { ++ ySpeed = 0; + } ++ return new Player(pos, new Vec(xSpeed, ySpeed)); + }; + ``` + +-After moving, the method checks for other actors that the player is colliding with and again calls `playerTouched` when it finds one. This time, it passes the actor object as the second argument because if the other actor is a coin, `playerTouched` needs to know _which_ coin is being collected. +- +-Finally, when the player dies (touches lava), we set up a little animation that causes them to “shrink” or “sink” down by reducing the height of the player object. ++The horizontal motion is computed based on the state of the left and right arrow keys. When there's no wall blocking the new position created by this motion, it is used. Otherwise, the old position is kept. + +-And here is the method that handles collisions between the player and other objects: +- +-``` +-Level.prototype.playerTouched = function(type, actor) { +- if (type == "lava" && this.status == null) { +- this.status = "lost"; +- this.finishDelay = 1; +- } else if (type == "coin") { +- this.actors = this.actors.filter(function(other) { +- return other != actor; +- }); +- if (!this.actors.some(function(actor) { +- return actor.type == "coin"; +- })) { +- this.status = "won"; +- this.finishDelay = 1; +- } +- } +-}; +-``` ++Vertical motion works in a similar way but has to simulate jumping and gravity. The player's vertical speed (`ySpeed`) is first accelerated to account for gravity. + +-When lava is touched, the game's status is set to `"lost"`. When a coin is touched, that coin is removed from the array of actors, and if it was the last one, the game's status is set to `"won"`. ++We check for walls again. If we don't hit any, the new position is used. If there _is_ a wall, there are two possible outcomes. When the up arrow is pressed _and_ we are moving down (meaning the thing we hit is below us), the speed is set to a relatively large, negative value. This causes the player to jump. If that is not the case, the player simply bumped into something, and the speed is set to zero. + +-This gives us a level that can actually be animated. All that is missing now is the code that _drives_ the animation. ++The gravity strength, jumping speed, and pretty much all other constants in this game have been set by trial and error. I tested values until I found a combination I liked. + + ## Tracking keys + +-For a game like this, we do not want keys to take effect once per keypress. Rather, we want their effect (moving the player figure) to continue happening as long as they are pressed. ++For a game like this, we do not want keys to take effect once per keypress. Rather, we want their effect (moving the player figure) to stay active as long as they are held. + + We need to set up a key handler that stores the current state of the left, right, and up arrow keys. We will also want to call `preventDefault` for those keys so that they don't end up scrolling the page. + +-The following function, when given an object with key codes as property names and key names as values, will return an object that tracks the current position of those keys. It registers event handlers for `"keydown"` and `"keyup"` events and, when the key code in the event is present in the set of codes that it is tracking, updates the object. ++The following function, when given an array of key names, will return an object that tracks the current position of those keys. It registers event handlers for `"keydown"` and `"keyup"` events and, when the key code in the event is present in the set of codes that it is tracking, updates the object. + + ``` +-var arrowCodes = {37: "left", 38: "up", 39: "right"}; +- +-function trackKeys(codes) { +- var pressed = Object.create(null); +- function handler(event) { +- if (codes.hasOwnProperty(event.keyCode)) { +- var down = event.type == "keydown"; +- pressed[codes[event.keyCode]] = down; ++function trackKeys(keys) { ++ let down = Object.create(null); ++ function track(event) { ++ if (keys.includes(event.key)) { ++ down[event.key] = event.type == "keydown"; + event.preventDefault(); + } + } +- addEventListener("keydown", handler); +- addEventListener("keyup", handler); +- return pressed; ++ window.addEventListener("keydown", track); ++ window.addEventListener("keyup", track); ++ return down; + } ++ ++const arrowKeys = ++ trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]); + ``` + +-Note how the same handler function is used for both event types. It looks at the event object's `type` property to determine whether the key state should be updated to true (`"keydown"`) or false (`"keyup"`). ++The same handler function is used for both event types. It looks at the event object's `type` property to determine whether the key state should be updated to true (`"keydown"`) or false (`"keyup"`). + + ## Running the game + +-The `requestAnimationFrame` function, which we saw in [Chapter 13](13_dom.html#animationFrame), provides a good way to animate a game. But its interface is quite primitive—using it requires us to track the time at which our function was called the last time around and call `requestAnimationFrame` again after every frame. ++The `requestAnimationFrame` function, which we saw in [Chapter 14](14_dom.html#animationFrame), provides a good way to animate a game. But its interface is quite primitive—using it requires us to track the time at which our function was called the last time around and call `requestAnimationFrame` again after every frame. + + Let's define a helper function that wraps those boring parts in a convenient interface and allows us to simply call `runAnimation`, giving it a function that expects a time difference as an argument and draws a single frame. When the frame function returns the value `false`, the animation stops. + + ``` + function runAnimation(frameFunc) { +- var lastTime = null; ++ let lastTime = null; + function frame(time) { +- var stop = false; + if (lastTime != null) { +- var timeStep = Math.min(time - lastTime, 100) / 1000; +- stop = frameFunc(timeStep) === false; ++ let timeStep = Math.min(time - lastTime, 100) / 1000; ++ if (frameFunc(timeStep) === false) return; + } + lastTime = time; +- if (!stop) +- requestAnimationFrame(frame); ++ requestAnimationFrame(frame); + } + requestAnimationFrame(frame); + } + ``` + +-I have set a maximum frame step of 100 milliseconds (one-tenth of a second). When the browser tab or window with our page is hidden, `requestAnimationFrame` calls will be suspended until the tab or window is shown again. In this case, the difference between `lastTime` and `time` will be the entire time in which the page was hidden. Advancing the game by that much in a single step will look silly and might be a lot of work (remember the time-splitting in the [`animate` method](15_game.html#actors)). ++I have set a maximum frame step of 100 milliseconds (one-tenth of a second). When the browser tab or window with our page is hidden, `requestAnimationFrame` calls will be suspended until the tab or window is shown again. In this case, the difference between `lastTime` and `time` will be the entire time in which the page was hidden. Advancing the game by that much in a single step will look silly and might cause weird side effects, such as the player falling through the floor. + + The function also converts the time steps to seconds, which are an easier quantity to think about than milliseconds. + +-The `runLevel` function takes a `Level` object, a constructor for a display, and, optionally, a function. It displays the level (in `document.body`) and lets the user play through it. When the level is finished (lost or won), `runLevel` clears the display, stops the animation, and, if an `andThen` function was given, calls that function with the level's status. +- +-``` +-var arrows = trackKeys(arrowCodes); +- +-function runLevel(level, Display, andThen) { +- var display = new Display(document.body, level); +- runAnimation(function(step) { +- level.animate(step, arrows); +- display.drawFrame(step); +- if (level.isFinished()) { +- display.clear(); +- if (andThen) +- andThen(level.status); +- return false; +- } ++The `runLevel` function takes a `Level` object and a display constructor, and returns a promise. It displays the level (in `document.body`) and lets the user play through it. When the level is finished (lost or won), `runLevel` waits one more second (to let the user see what happens) and then clears the display, stops the animation, and resolves the promise to the game's end status. ++ ++``` ++function runLevel(level, Display) { ++ let display = new Display(document.body, level); ++ let state = State.start(level); ++ let ending = 1; ++ return new Promise(resolve => { ++ runAnimation(time => { ++ state = state.update(time, arrowKeys); ++ display.setState(state); ++ if (state.status == "playing") { ++ return true; ++ } else if (ending > 0) { ++ ending -= time; ++ return true; ++ } else { ++ display.clear(); ++ resolve(state.status); ++ return false; ++ } ++ }); + }); + } + ``` + +-A game is a sequence of levels. Whenever the player dies, the current level is restarted. When a level is completed, we move on to the next level. This can be expressed by the following function, which takes an array of level plans (arrays of strings) and a display constructor: ++A game is a sequence of levels. Whenever the player dies, the current level is restarted. When a level is completed, we move on to the next level. This can be expressed by the following function, which takes an array of level plans (strings) and a display constructor: + + ``` +-function runGame(plans, Display) { +- function startLevel(n) { +- runLevel(new Level(plans[n]), Display, function(status) { +- if (status == "lost") +- startLevel(n); +- else if (n < plans.length - 1) +- startLevel(n + 1); +- else +- console.log("You win!"); +- }); ++async function runGame(plans, Display) { ++ for (let level = 0; level < plans.length;) { ++ let status = await runLevel(new Level(plans[level]), ++ Display); ++ if (status == "won") level++; + } +- startLevel(0); ++ console.log("You've won!"); + } + ``` + +-These functions show a peculiar style of programming. Both `runAnimation` and `runLevel` are higher-order functions but are not in the style we saw in [Chapter 5](05_higher_order.html#higher_order). The function argument is used to arrange things to happen at some time in the future, and neither of the functions returns anything useful. Their task is, in a way, to schedule actions. Wrapping these actions in functions gives us a way to store them as a value so that they can be called at the right moment. ++Because we made `runLevel` return a promise, `runGame` can be written using an `async` function, as seen in [Chapter 11](11_async.html). It returns another promise, which resolves when the player finished the game. + +-This programming style is usually called _asynchronous_ programming. Event handling is also an instance of this style, and we will see much more of it when working with tasks that can take an arbitrary amount of time, such as network requests in [Chapter 17](17_http.html#http) and input and output in general in [Chapter 20](20_node.html#node). +- +-There is a set of level plans available in the `GAME_LEVELS` variable . This page feeds them to `runGame`, starting an actual game: ++There is a set of level plans available in the `GAME_LEVELS` binding in [this chapter's sandbox](https://eloquentjavascript.net/code#16). This page feeds them to `runGame`, starting an actual game: + + ``` + +@@ -715,7 +713,7 @@ See if you can beat those. I had quite a lot of fun building them. + + It's traditional for platform games to have the player start with a limited number of _lives_ and subtract one life each time they die. When the player is out of lives, the game restarts from the beginning. + +-Adjust `runGame` to implement lives. Have the player start with three. ++Adjust `runGame` to implement lives. Have the player start with three. Output the current amount of lives (using `console.log`) every time a level starts. + + ``` + +@@ -723,30 +721,19 @@ Adjust `runGame` to implement lives. Have the player start with three. + + + + ``` + +-The most obvious solution would be to make `lives` a variable that lives in `runGame` and is thus visible to the `startLevel` closure. +- +-Another approach, which fits nicely with the spirit of the rest of the function, would be to add a second parameter to `startLevel` that gives the number of lives. When the whole state of a system is stored in the arguments to a function, calling that function provides an elegant way to transition to a new state. +- +-In any case, when a level is lost, there should now be two possible state transitions. If that was the last life, we go back to level zero with the starting amount of lives. If not, we repeat the current level with one less life remaining. +- + ### Pausing the game + + Make it possible to pause (suspend) and unpause the game by pressing the Esc key. +@@ -755,7 +742,7 @@ This can be done by changing the `runLevel` function to use another keyboard eve + + The `runAnimation` interface may not look like it is suitable for this at first glance, but it is, if you rearrange the way `runLevel` calls it. + +-When you have that working, there is something else you could try. The way we have been registering keyboard event handlers is somewhat problematic. The `arrows` object is currently a global variable, and its event handlers are kept around even when no game is running. You could say they _leak_ out of our system. Extend `trackKeys` to provide a way to unregister its handlers, and then change `runLevel` to register its handlers when it starts and unregister them again when it is finished. ++When you have that working, there is something else you could try. The way we have been registering keyboard event handlers is somewhat problematic. The `arrows` object is currently a global binding, and its event handlers are kept around even when no game is running. You could say they _leak_ out of our system. Extend `trackKeys` to provide a way to unregister its handlers, and then change `runLevel` to register its handlers when it starts and unregister them again when it is finished. + + ``` + +@@ -763,17 +750,25 @@ When you have that working, there is something else you could try. The way we ha + + ++ ++``` ++ ++If you want to implement a type of motion that is stateful, such as bouncing, make sure you store the necessary state in the actor object—include it as constructor argument and add it as a property. ++ ++Remember that `update` returns a _new_ object, rather than changing the old one. ++ ++When handling collision, find the player in `state.actors` and compare its position to the monster's position. To get the _bottom_ of the player, you have to add its vertical size to its vertical position. The creation of an updated state will resemble either `Coin`'s `collide` method (removing the actor) or `Lava`'s (changing the status to `"lost"`), depending on the player position.
nameheightcountryplace
Kilimanjaro