UNB/ CS/ David Bremner/ teaching/ cs2613/ labs/ Lab 12

Before the lab

Symbols

Time
10 minutes
Activity
Demo

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);
     });

Iterable class

Time
40 minutes
Activity
Exercise from book.

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"]);
});

Methods without classes

Time
10 minutes
Activity
Demo

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);

Protypes

Time
25 minutes
Activity
Individual work

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.

Classes (are really about prototypes)

Time
10 minutes
Activity
Demo

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.

Before next lab

Read the followings sections from Chapter 11 of Eloquent Javascript:

From the MDN JavaScript Guide, read Using Promises