You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.
479
533
480
534
## Symbols
481
535
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
+
482
540
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.
483
541
484
542
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.
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.
557
613
558
614
```{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;
571
619
}
572
620
573
-
get(x, y) {
574
-
return this.#content[y * this.width + x];
621
+
get length() {
622
+
return 1 + (this.rest ? this.rest.length : 0);
575
623
}
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;
578
631
}
579
632
}
580
633
```
581
634
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.
583
636
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.
585
638
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"}}
589
640
590
641
```{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;
596
645
}
597
646
598
647
next() {
599
-
if (this.y == this.matrix.height) {
648
+
if (this.list == null) {
600
649
return {done: true};
601
650
}
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;
611
653
return {value, done: false};
612
654
}
613
655
}
614
656
```
615
657
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).
617
659
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.
619
661
620
662
```{includeCode: true}
621
-
Matrix.prototype[Symbol.iterator] = function() {
622
-
return new MatrixIterator(this);
663
+
List.prototype[Symbol.iterator] = function() {
664
+
return new ListIterator(this);
623
665
};
624
666
```
625
667
626
668
{{index "for/of loop"}}
627
669
628
-
We can now loop over a matrix with `for`/`of`.
670
+
We can now loop over a list with `for`/`of`.
629
671
630
672
```
631
-
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
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.
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_.
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.
715
696
716
697
{{index overriding, prototype}}
717
698
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.
719
700
720
701
In object-oriented programming terms, this is called _((inheritance))_. The new class inherits properties and behavior from the old class.
721
702
722
703
```{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;
729
710
}
730
711
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;
736
714
}
737
715
}
738
716
739
-
let matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`);
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))_.
745
722
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.
747
724
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.
749
726
750
727
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.
751
728
@@ -761,19 +738,19 @@ It is occasionally useful to know whether an object was derived from a specific
761
738
762
739
```
763
740
console.log(
764
-
new SymmetricMatrix(2) instanceof SymmetricMatrix);
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`.
Copy file name to clipboardExpand all lines: 18_http.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -963,7 +963,7 @@ if}}
963
963
964
964
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.
965
965
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.
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.
0 commit comments