You might remember symbols from Racket. It turns out JavaScript has
something similar (but more
confusing).
Just like strings, Symbols can be used as keys (e.g. method names for
objects). Here we rely on a predefined value to be able to call a
method. In the last part of the lab, you will use the global
Symbol.iterator
as a method name.
const test=require("node:test");
const assert=require("assert");
let sym=Symbol("my cool symbol name");
test("uniqueness",
(t)=>assert.notEqual(sym, Symbol("my cool symbol name")));
class Thing {
constructor() {
this.count=0;
}
// note the syntax to define indirect method names
[sym]() {
return this.count++;
}
}
test("call",
(t)=> {
let x = new Thing();
assert.strictEqual(x[sym](),0);
assert.strictEqual(x[sym](),1);
});
Do the Iterable Group exercise.
Start by adding the following two methods to your Group class.
// Utility function for iterator class
keys() {
return Object.keys(this.elements);
}
// Mark this class as iterable.
[Symbol.iterator]() {
return new GroupIterator(this);
}
Next create a GroupIterator
class.
class GroupIterator {
constructor(group) {
this.index=0;
this.group=group;
}
next() {
}
}
Refer to the
MatrixIterator
class for inspiration, but note that you should always return an
object with a value
property. When you are done, the following test
should pass.
test("iterate", (t) => {
let vals=[];
for (let value of Group.from(["a", "b", "c"])) {
vals.push(value+value);
}
assert.deepStrictEqual(vals,["aa","bb","cc"]);
});
We can use JavaScript objects in a simple way by e.g. naming conventions for functions.
function timePlus(time1, time2) {
let mins = (time1.mins + time2.mins) % 60;
let hours = time1.hours + time2.hours + Math.floor((time1.mins+time2.mins)/60);
return {'hours': hours, 'mins': mins};
}
console.log(timePlus({hours: 10, mins:30}, {hours: 17, mins:47}));
If we want to have "time" objects with a "plus" method, one way is to
add that method manually. In order for that to work, we need to make
use of the JavaScript variable this. When the function method
is
called as object.method()
, the special variable this
is defined to
mean the current object. We can use this
to wrap our existing
function as a method.
function maketime(hours, mins){
let plus=function (other) {
let raw=timePlus(this,other);
return ______________________________
};
return { 'hours': hours, 'mins': mins, 'plus': plus};
}
let A=maketime(10, 30);
let B=maketime(17, 47);
let C=A.plus(B);
console.log(C);
Adding methods manually to newly created objects is a bit confusing and verbose, so JavaScript has the notion of a 'prototype',
let protoTime = {
plus: function(other) {
let raw=timePlus(this,other);
return timeNew(raw.hours, raw.mins);
}
};
In the following code, the method plus
is inherited from the
prototype, but the fields hours
and minutes
are not.
Fill in the blanks to get the following code working.
function timeNew(hours, mins) {
let obj=Object.create(protoTime);
_______________;
_____________;
return obj;
}
D=timeNew(21,42);
E=timeNew(17,37);
F=D.plus(E);
console.log(F);
The benefit is not huge here, but you can see how it would help if there were many methods, all creating new objects.
In practice it's not usual to call object.create directly with the
prototype object, but rather to use new
keyword, along with the
prototype
property of a constructor.
function Time(hours, mins){
this.hours=hours;
this.mins=mins;
}
Time.prototype.plus=function (other) {
let raw=timePlus(this,other);
return ______________________________;
}
G = new Time(20,59);
H = new Time(11,11);
I=G.plus(H);
console.log(I);
As we saw already, modern javascript provides the class
keyword to concisely generate
constructors and prototypes.
class Time2 {
constructor(hours, mins){
this.hours=hours;
this.mins=mins;
};
plus(other) {
let raw=timePlus(this,other);
______________________________________;
}
}
Time2;
J= new Time2(5,30);
K= new Time2(11,55);
L=J.plus(K);
It's important to know that this is just convenience syntax for the same prototype based system.
Read the followings sections from Chapter 11 of Eloquent Javascript:
From the MDN JavaScript Guide, read Using Promises