Skip to content

Commit b48b883

Browse files
committed
Replace matrix example in chapter 6 with a simpler list example
1 parent 8d61f0f commit b48b883

File tree

3 files changed

+117
-140
lines changed

3 files changed

+117
-140
lines changed

06_object.md

Lines changed: 114 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -473,12 +473,70 @@ Array.prototype.forEach.call({
473473
// → B
474474
```
475475

476-
{{index "for/of loop", "iterator interface"}}
476+
## Getters, setters, and statics
477477

478-
I mentioned in [Chapter ?](data#for_of_loop) that a `for`/`of` loop can loop over several kinds of data structures. This is another case of polymorphism—such loops expect the data structure to expose a specific interface, which arrays and strings do. And we can also add this interface to our own objects! But before we can do that, we need to briefly take a look at the symbol type.
478+
{{index [interface, object], [property, definition], "Map class"}}
479+
480+
Interfaces often contain plain properties, not just methods. For example, `Map` objects have a `size` property that tells you how many keys are stored in them.
481+
482+
It is not necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called _((getter))s_, and they are defined by writing `get` in front of the method name in an object expression or class declaration.
483+
484+
```{test: no}
485+
let varyingSize = {
486+
get size() {
487+
return Math.floor(Math.random() * 100);
488+
}
489+
};
490+
491+
console.log(varyingSize.size);
492+
// → 73
493+
console.log(varyingSize.size);
494+
// → 49
495+
```
496+
497+
{{index "temperature example"}}
498+
499+
Whenever someone reads from this object's `size` property, the associated method is called. You can do a similar thing when a property is written to, using a _((setter))_.
500+
501+
```{test: no, startCode: true}
502+
class Temperature {
503+
constructor(celsius) {
504+
this.celsius = celsius;
505+
}
506+
get fahrenheit() {
507+
return this.celsius * 1.8 + 32;
508+
}
509+
set fahrenheit(value) {
510+
this.celsius = (value - 32) / 1.8;
511+
}
512+
513+
static fromFahrenheit(value) {
514+
return new Temperature((value - 32) / 1.8);
515+
}
516+
}
517+
518+
let temp = new Temperature(22);
519+
console.log(temp.fahrenheit);
520+
// → 71.6
521+
temp.fahrenheit = 86;
522+
console.log(temp.celsius);
523+
// → 30
524+
```
525+
526+
The `Temperature` class allows you to read and write the temperature in either degrees ((Celsius)) or degrees ((Fahrenheit)), but internally it stores only Celsius and automatically converts to and from Celsius in the `fahrenheit` getter and setter.
527+
528+
{{index "static method", "static property"}}
529+
530+
Sometimes you want to attach some properties directly to your constructor function, rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances.
531+
532+
Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. So the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit.
479533

480534
## Symbols
481535

536+
{{index "for/of loop", "iterator interface"}}
537+
538+
I mentioned in [Chapter ?](data#for_of_loop) that a `for`/`of` loop can loop over several kinds of data structures. This is another case of polymorphism—such loops expect the data structure to expose a specific interface, which arrays and strings do. And we can also add this interface to our own objects! But before we can do that, we need to briefly take a look at the symbol type.
539+
482540
It is possible for multiple interfaces to use the same property name for different things. For example, on array-like objects, `length` refers to the amount of elements in the collection. But an object interface describing a hiking route could use `length` to provide the length of the route in meters. It would not be possible for an object to conform to both these interfaces.
483541

484542
An object trying to be a route and array-like (maybe to enumerate its waypoints) is somewhat far-fetched, and this kind of problem isn't that common in practice. But for things like the iteration protocol, the language designers needed a type of property that _really_ doesn't conflict with any others. So in 2015, _((symbol))s_ were added to the language.
@@ -549,93 +607,76 @@ console.log(okIterator.next());
549607
// → {value: undefined, done: true}
550608
```
551609

552-
{{index "matrix example", "Matrix class", [array, "as matrix"]}}
610+
{{index ["data structure", list], "linked list", collection}}
553611

554-
{{id matrix}}
555-
556-
Let's implement an iterable data structure. We'll build a _matrix_ class, acting as a two-dimensional array.
612+
Let's implement an iterable data structure similar to the linked list from the exercise in [Chapter ?](data). We'll write the list as a class this time.
557613

558614
```{includeCode: true}
559-
class Matrix {
560-
#content;
561-
constructor(width, height, element = (x, y) => undefined) {
562-
this.width = width;
563-
this.height = height;
564-
this.#content = [];
565-
566-
for (let y = 0; y < height; y++) {
567-
for (let x = 0; x < width; x++) {
568-
this.#content[y * width + x] = element(x, y);
569-
}
570-
}
615+
class List {
616+
constructor(value, rest) {
617+
this.value = value;
618+
this.rest = rest;
571619
}
572620
573-
get(x, y) {
574-
return this.#content[y * this.width + x];
621+
get length() {
622+
return 1 + (this.rest ? this.rest.length : 0);
575623
}
576-
set(x, y, value) {
577-
this.#content[y * this.width + x] = value;
624+
625+
static fromArray(array) {
626+
let result = null
627+
for (let i = array.length - 1; i >= 0; i--) {
628+
result = new this(array[i], result);
629+
}
630+
return result;
578631
}
579632
}
580633
```
581634

582-
The class stores its content in a single array of _width_ × _height_ elements. The elements are stored row by row, so, for example, the third element in the fifth row is (using zero-based indexing) stored at position 4 × _width_ + 2.
635+
Note that `this`, in a static method, points at the constructor of the class, not an instance—there is no instance around, when a static method is called.
583636

584-
The constructor function takes a width, a height, and an optional `element` function that will be used to fill in the initial values. There are `get` and `set` methods to retrieve and update elements in the matrix.
637+
Iterating over a list should return all the list's elements from start to end. We'll write a separate class for the iterator.
585638

586-
When looping over a matrix, you are usually interested in the position of the elements as well as the elements themselves, so we'll have our iterator produce objects with `x`, `y`, and `value` properties.
587-
588-
{{index "MatrixIterator class"}}
639+
{{index "ListIterator class"}}
589640

590641
```{includeCode: true}
591-
class MatrixIterator {
592-
constructor(matrix) {
593-
this.x = 0;
594-
this.y = 0;
595-
this.matrix = matrix;
642+
class ListIterator {
643+
constructor(list) {
644+
this.list = list;
596645
}
597646
598647
next() {
599-
if (this.y == this.matrix.height) {
648+
if (this.list == null) {
600649
return {done: true};
601650
}
602-
603-
let value = {x: this.x,
604-
y: this.y,
605-
value: this.matrix.get(this.x, this.y)};
606-
this.x++;
607-
if (this.x == this.matrix.width) {
608-
this.x = 0;
609-
this.y++;
610-
}
651+
let value = this.list.value;
652+
this.list = this.list.rest;
611653
return {value, done: false};
612654
}
613655
}
614656
```
615657

616-
The class tracks the progress of iterating over a matrix in its `x` and `y` properties. The `next` method starts by checking whether the bottom of the matrix has been reached. If it hasn't, it _first_ creates the object holding the current value and _then_ updates its position, moving to the next row if necessary.
658+
The class tracks the progress of iterating through the list by updating its `list` property to move to the next list object whenever a value is returned, and reports that it is done when that list is empty (null).
617659

618-
Let's set up the `Matrix` class to be iterable. Throughout this book, I'll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self-contained. In a regular program, where there is no need to split the code into small pieces, you'd declare these methods directly in the class instead.
660+
Let's set up the `List` class to be iterable. Throughout this book, I'll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self-contained. In a regular program, where there is no need to split the code into small pieces, you'd declare these methods directly in the class instead.
619661

620662
```{includeCode: true}
621-
Matrix.prototype[Symbol.iterator] = function() {
622-
return new MatrixIterator(this);
663+
List.prototype[Symbol.iterator] = function() {
664+
return new ListIterator(this);
623665
};
624666
```
625667

626668
{{index "for/of loop"}}
627669

628-
We can now loop over a matrix with `for`/`of`.
670+
We can now loop over a list with `for`/`of`.
629671

630672
```
631-
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
632-
for (let {x, y, value} of matrix) {
633-
console.log(x, y, value);
673+
let list = List.fromArray([1, 2, 3]);
674+
for (let element of list) {
675+
console.log(element);
634676
}
635-
// → 0 0 value 0,0
636-
// → 1 0 value 1,0
637-
// → 0 1 value 0,1
638-
// → 1 1 value 1,1
677+
// → 1
678+
// → 2
679+
// → 3
639680
```
640681

641682
{{index spread}}
@@ -647,105 +688,41 @@ console.log([..."PCI"]);
647688
// → ["P", "C", "I"]
648689
```
649690

650-
## Getters, setters, and statics
651-
652-
{{index [interface, object], [property, definition], "Map class"}}
653-
654-
Interfaces often contain plain properties, not just methods. For example, `Map` objects have a `size` property that tells you how many keys are stored in them.
655-
656-
It is not necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called _((getter))s_, and they are defined by writing `get` in front of the method name in an object expression or class declaration.
657-
658-
```{test: no}
659-
let varyingSize = {
660-
get size() {
661-
return Math.floor(Math.random() * 100);
662-
}
663-
};
664-
665-
console.log(varyingSize.size);
666-
// → 73
667-
console.log(varyingSize.size);
668-
// → 49
669-
```
670-
671-
{{index "temperature example"}}
672-
673-
Whenever someone reads from this object's `size` property, the associated method is called. You can do a similar thing when a property is written to, using a _((setter))_.
674-
675-
```{test: no, startCode: true}
676-
class Temperature {
677-
constructor(celsius) {
678-
this.celsius = celsius;
679-
}
680-
get fahrenheit() {
681-
return this.celsius * 1.8 + 32;
682-
}
683-
set fahrenheit(value) {
684-
this.celsius = (value - 32) / 1.8;
685-
}
686-
687-
static fromFahrenheit(value) {
688-
return new Temperature((value - 32) / 1.8);
689-
}
690-
}
691-
692-
let temp = new Temperature(22);
693-
console.log(temp.fahrenheit);
694-
// → 71.6
695-
temp.fahrenheit = 86;
696-
console.log(temp.celsius);
697-
// → 30
698-
```
699-
700-
The `Temperature` class allows you to read and write the temperature in either degrees ((Celsius)) or degrees ((Fahrenheit)), but internally it stores only Celsius and automatically converts to and from Celsius in the `fahrenheit` getter and setter.
701-
702-
{{index "static method", "static property"}}
703-
704-
Sometimes you want to attach some properties directly to your constructor function, rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances.
705-
706-
Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. So the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit.
707-
708691
## Inheritance
709692

710-
{{index inheritance, "matrix example", "object-oriented programming", "SymmetricMatrix class"}}
711-
712-
Some matrices are known to be _symmetric_. If you mirror a symmetric matrix around its top-left-to-bottom-right diagonal, it stays the same. In other words, the value stored at _x_,_y_ is always the same as that at _y_,_x_.
693+
{{index inheritance, "linked list", "object-oriented programming", "LengthList class"}}
713694

714-
Imagine we need a data structure like `Matrix` but one that enforces the fact that the matrix is and remains symmetrical. We could write it from scratch, but that would involve repeating some code very similar to what we already wrote.
695+
Imagine we needed a list type, much like the `List` class we saw before, but because we will be asking for its length all the time, we don't want it to have to scan through its `rest` every time, and instead want to store the length in every instance for efficient access.
715696

716697
{{index overriding, prototype}}
717698

718-
JavaScript's prototype system makes it possible to create a _new_ class, much like the old class, but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the `set` method.
699+
JavaScript's prototype system makes it possible to create a _new_ class, much like the old class, but with new definitions for some of its properties. The prototype for the new class derives from the old prototype but adds a new definition for, say, the `length` getter.
719700

720701
In object-oriented programming terms, this is called _((inheritance))_. The new class inherits properties and behavior from the old class.
721702

722703
```{includeCode: "top_lines: 17"}
723-
class SymmetricMatrix extends Matrix {
724-
constructor(size, element = (x, y) => undefined) {
725-
super(size, size, (x, y) => {
726-
if (x <= y) return element(y, x);
727-
else return element(x, y);
728-
});
704+
class LengthList extends List {
705+
#length;
706+
707+
constructor(value, rest) {
708+
super(value, rest);
709+
this.#length = super.length;
729710
}
730711
731-
set(x, y, value) {
732-
super.set(x, y, value);
733-
if (x != y) {
734-
super.set(y, x, value);
735-
}
712+
get length() {
713+
return this.#length;
736714
}
737715
}
738716
739-
let matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`);
740-
console.log(matrix.get(2, 3));
741-
// → 3,2
717+
console.log(LengthList.fromArray([1, 2, 3]).length);
718+
// → 3
742719
```
743720

744721
The use of the word `extends` indicates that this class shouldn't be directly based on the default `Object` prototype but on some other class. This is called the _((superclass))_. The derived class is the _((subclass))_.
745722

746-
To initialize a `SymmetricMatrix` instance, the constructor calls the constructor of its superclass through the `super` keyword. This is necessary because if this new object is to behave (roughly) like a `Matrix`, it is going to need the instance properties that matrices have. To ensure the matrix is symmetrical, the constructor wraps the `element` function to swap the coordinates for values below the diagonal.
723+
To initialize a `LengthList` instance, the constructor calls the constructor of its superclass through the `super` keyword. This is necessary because if this new object is to behave (roughly) like a `List`, it is going to need the instance properties that lists have.
747724

748-
The `set` method again uses `super` but this time not to call the constructor but to call a specific method from the superclass's set of methods. We are redefining `set` but do want to use the original behavior. Because `this.set` refers to the _new_ `set` method, calling that wouldn't work. Inside class methods, `super` provides a way to call methods as they were defined in the superclass.
725+
The constructor then stores the list's length in a private property. If we had written `this.length` there, the class's own getter would have been called, which doesn't work yet, since `#length` hasn't been filled in yet. We can using `super.something` to call methods and getters on the superclass's prototype, which is often useful.
749726

750727
Inheritance allows us to build slightly different data types from existing data types with relatively little work. It is a fundamental part of the object-oriented tradition, alongside encapsulation and polymorphism. But while the latter two are now generally regarded as wonderful ideas, inheritance is more controversial.
751728

@@ -761,19 +738,19 @@ It is occasionally useful to know whether an object was derived from a specific
761738

762739
```
763740
console.log(
764-
new SymmetricMatrix(2) instanceof SymmetricMatrix);
741+
new LengthList(1, null) instanceof LengthList);
765742
// → true
766-
console.log(new SymmetricMatrix(2) instanceof Matrix);
743+
console.log(new LengthList(2, null) instanceof List);
767744
// → true
768-
console.log(new Matrix(2, 2) instanceof SymmetricMatrix);
745+
console.log(new List(3, null) instanceof LengthList);
769746
// → false
770747
console.log([1] instanceof Array);
771748
// → true
772749
```
773750

774751
{{index inheritance}}
775752

776-
The operator will see through inherited types, so a `SymmetricMatrix` is an instance of `Matrix`. The operator can also be applied to standard constructors like `Array`. Almost every object is an instance of `Object`.
753+
The operator will see through inherited types, so a `LengthList` is an instance of `List`. The operator can also be applied to standard constructors like `Array`. Almost every object is an instance of `Object`.
777754

778755
## Summary
779756

18_http.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ if}}
963963

964964
To solve the problem of having the changes conceptually happen at the same time, try to see the computation of a ((generation)) as a ((pure function)), which takes one ((grid)) and produces a new grid that represents the next turn.
965965

966-
Representing the matrix can be done in the way shown in [Chapter ?](object#matrix). You can count live ((neighbor))s with two nested loops, looping over adjacent coordinates in both dimensions. Take care not to count cells outside of the field and to ignore the cell in the center, whose neighbors we are counting.
966+
Representing the matrix can be done with a single array of width × height elements, storing values row by row, so, for example, the third element in the fifth row is (using zero-based indexing) stored at position 4 × _width_ + 2. You can count live ((neighbor))s with two nested loops, looping over adjacent coordinates in both dimensions. Take care not to count cells outside of the field and to ignore the cell in the center, whose neighbors we are counting.
967967

968968
{{index "event handling", "change event"}}
969969

19_paint.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ Each component has a `syncState` method that is used to synchronize it to a new
7272

7373
## The state
7474

75-
{{index "Picture class", "picture property", "tool property", "color property", "Matrix class"}}
75+
{{index "Picture class", "picture property", "tool property", "color property"}}
7676

77-
The application state will be an object with `picture`, `tool`, and `color` properties. The picture is itself an object that stores the width, height, and pixel content of the picture. The ((pixel))s are stored in an array, in the same way as the matrix class from [Chapter ?](object)row by row, from top to bottom.
77+
The application state will be an object with `picture`, `tool`, and `color` properties. The picture is itself an object that stores the width, height, and pixel content of the picture. The ((pixel))s are stored in a single array, row by row, from top to bottom.
7878

7979
```{includeCode: true}
8080
class Picture {

0 commit comments

Comments
 (0)