JavaScript OOP Course 9: Exercises

From WikiMLT
Revision as of 10:09, 18 June 2022 by Spas (talk | contribs) (Стадий: 6 [Фаза:Утвърждаване, Статус:Утвърден]; Категория:JavaScript)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Ref­er­ences

Stop­watch

Use Con­struc­tor func­tion and cre­ate an Ob­ject sw that has the fol­low­ing mem­bers:

  • Prop­er­ties: du­ra­tion
  • Method: re­set(), start(), stop().
function Stopwatch() {
    let startTime = null; let endTime = null; let running = false; let duration = 0;

    this.start = function() {
        if (running) throw new Error('The Stopwatch is already startrd.');
        running = true;
        startTime = new Date();
    };

    this.stop = function() {
        if (!running) throw new Error('The Stopwatch is not running.');
        running = false;
        endTime = new Date();
        const seconds = (endTime.getTime() - startTime.getTime()) / 1000;
        duration += seconds;
    };

    this.reset = function() {
        startTime = null; endTime = null; running = false; duration = 0;
    };
    
    Object.defineProperty(this, 'duration', {
        get: function() { return duration; }
    });
}
const sw = new Stopwatch();
sw.start();
sw.stop();
sw.duration;
8.56
sw.reset();

Stop­watch (move all meth­ods at pro­to­type lev­el)

In or­der to do that we need to give pub­lic ac­cess to the pri­vate prop­er­ties (de­fined by the let key­word) as it is done be­low, but note this breaks the ab­strac­tion prin­ci­ple, for ex­am­ple: In or­der to set the du­ra­tion val­ue from the pro­to­type lev­el we need to cre­ate set­ter that pri­vate vari­able, but in the same time this set­ter func­tion is ac­ces­si­ble from out­side… This ex­am­ple is de­signed to show that some­times it is a bad prac­tice to move some mem­bers at the pro­to­type lev­el – Pre­ma­ture op­ti­miza­tion is the root of all evils.

function Stopwatch() {
    let duration = 0; let startTime = null; let endTime = null; let running = false;

    Object.defineProperty(this, 'duration', {
        get: function() { return duration; },
        set: function(value) { duration = value; }
    });

    Object.defineProperty(this, 'startTime', {
        get: function() { return startTime; },
        set: function(value) { startTime = value; }
    });

    Object.defineProperty(this, 'endTime', {
        get: function() { return endTime; },
        set: function(value) { endTime = value; }
    });

    Object.defineProperty(this, 'running', {
        get: function() { return running; },
        set: function(value) { running = value; }
    });
}

Stopwatch.prototype.start = function() {
    if (this.running) throw new Error('The Stopwatch is already started.');
    this.running = true;
    this.startTime = new Date();
};

Stopwatch.prototype.stop = function() {
    if (!this.running) throw new Error('The Stopwatch not started.');
    this.running = false;
    this.endTime = new Date;
    const seconds = (this.endTime.getTime() - this.startTime.getTime()) / 1000;
    this.duration += seconds;
};

Stopwatch.prototype.reset = function() {
    this.duration = 0;
    this.startTime = null;
    this.endTime = null;
    this.running = false;
};

Pro­to­typ­i­cal In­her­i­tance

Cre­ate two con­struc­tors Htm­lEle­ment() and HtmlS­e­lectEle­ment() with the fol­low­ing spec­i­fi­ca­tion:

Htm­lEle­ment() should have two meth­ods:

  • click() that will pro­duce the text 'clicked' in the con­sole and is de­fined at the Con­struc­tor func­tion lev­el.
  • fo­cus() that will pro­duce the text 'fo­cused' in the con­sole and is de­fined at the Pro­to­type lev­el of the Con­struc­tor func­tion.

HtmlS­e­lectEle­ment() should have the fol­low­ing mem­bers:

  • A prop­er­ty called items which is an ar­ray that could be an emp­ty ar­ray or ar­ray passed as ar­gu­ment to the con­struc­tor func­tion when a new ob­ject is cre­at­ed by it.
  • Two meth­ods:
    • addItem(item) that will add the item passed as ar­gu­ment to the ar­ray of items.
    • removeItem(item) that will re­move the item passed as ar­gu­ment from the ar­ray of items.
  • Al­so should Pro­to­typ­i­cal­ly In­her­it the two meth­ods – click() and fo­cus() – from the Htm­lEle­ment() con­struc­tor.

1. Let's cre­ate and test Htm­lEle­ment().

function HtmlElement() {
    this.click = function() {
        console.log('clicked');
    };
}

HtmlElement.prototype.focus = function() {
    console.log('focused');
};
const e = new HtmlElement();
console.log(e);
HtmlElement {click: ƒ}
    click: ƒ ()
    [[Prototype]]: Object
        focus: ƒ ()
        constructor: ƒ HtmlElement()
        [[Prototype]]: Object
e.click();
e.focus();
clicked
focused

2. Let's cre­ate and test the ba­sic im­ple­men­ta­tion of the HtmlS­e­lectEle­ment() con­struc­tor.

function HtmlSelectElement(...args) {
    if (Array.isArray(args[0])) this.items = args[0];
    else this.items = args;

    this.addItem = function(item) {
        this.items.push(item);
    };

    this.removeItem = function(item) {
        let reducedItems = [];
        for (let thisItem of this.items) if (thisItem !== item) reducedItems.push(thisItem);
        this.items = reducedItems;
    };
}
const s1 = new HtmlSelectElement([1, 2, 3]);
console.log(s1);
HtmlSelectElement {items: Array(3), addItem: ƒ, removeItem: ƒ}
    items: (3) [1, 2, 3]
    addItem: ƒ (item)
    removeItem: ƒ (item)
    [[Prototype]]: Object
        constructor: ƒ HtmlSelectElement(...args)
        [[Prototype]]: Object

3. Let's im­ple­ment the In­her­i­tance and do a test. The fol­low is the most im­por­tant part of the ex­er­cise. Here we can't use the ap­proach de­scribed in the les­son JavaScript Pro­to­typ­i­cal In­her­i­tance – e.g. Html­Se­lect­Ele­ment­.­pro­to­type = Ob­ject.­cre­ate(Html­Ele­ment­.­pro­to­type); Be­cause in this case we will as­sign on­ly the Pro­to­type of Htm­lEle­ment() to the Pro­to­type of HtmlS­e­lectEle­ment(). But, as we can see above, we can't in­her­it the click() method in this way, be­cause it's de­fined at the Htm­lEle­ment() Con­struc­tor not at its pro­to­type lev­el. So in this case we need to:

HtmlSelectElement.prototype = new HtmlElement();
HtmlSelectElement.prototype.constructor = HtmlSelectElement;
const s2 = new HtmlSelectElement(['a', 'b', 'c']);
console.log(s2);
HtmlSelectElement {items: Array(3), addItem: ƒ, removeItem: ƒ}
    items: (3) ['a', 'b', 'c']
    addItem: ƒ (item)
    removeItem: ƒ (item)
    [[Prototype]]: HtmlElement
        click: ƒ ()
        constructor: ƒ HtmlSelectElement(...args)
        [[Prototype]]: Object
            focus: ƒ ()
            constructor: ƒ HtmlElement()
            [[Prototype]]: Object
s2.click();
s2.focus();
clicked
focused

Poly­mor­phism

1. Con­tin­u­ing from the last ex­er­cise and ex­tend HtmlS­e­lectEle­ment() and im­ple­ment a .ren­der() method that will out­put <se­lect> el­e­ment and a num­ber of <op­tion> el­e­ments that match­ing the el­e­ments from the items ar­ray.

HtmlSelectElement.prototype.render = function() {
    if (Array.isArray(this.items) && this.items.lengtt !== 0)
        return '<select>\n'
         + this.items.map(item => `\t<option>${item}</option>`).join('\n')
         + '\n</select>';
};
// Note here we can use template string inside another like this:
return `<select>${this.items.map(item => `\t<option>${item}</option>`).join('\n')}</select>`;
console.log(s3);
HtmlSelectElement {items: Array(3), addItem: ƒ, removeItem: ƒ}
    items: (3) ['a', 'b', 'c']
    addItem: ƒ (item)
    removeItem: ƒ (item)
    [[Prototype]]: HtmlElement
        click: ƒ ()
        render: ƒ ()
        constructor: ƒ HtmlSelectElement(...args)
        [[Prototype]]: Object
            focus: ƒ ()
            constructor: ƒ HtmlElement()
            [[Prototype]]: Object
console.log( s3.render() );
<select>
	<option>a</option>
	<option>b</option>
	<option>c</option>
</select>

2. Now let's cre­ate an­oth­er con­struc­tor that will gen­er­ate <img> el­e­ments, name it Htm­lIm­ageEle­ment(). It must in­her­it .click() and .fo­cus() from Htm­lEle­ment() and must have its own .ren­der() method. The new ob­jects must have src prop­er­ty that will be used with­in the .ren­der() method.

function HtmlImageElement(src = '#') {
    this.src = src;
}

HtmlImageElement.prototype = new HtmlElement();
HtmlImageElement.prototype.constructor = HtmlImageElement;

HtmlImageElement.prototype.render = function() {
    if (this.src.lengtt !== 0) return `<img src="${this.src}" />\n`;
}
const img = new HtmlImageElement('https://metalevel.tech');
console.log(img);
HtmlImageElement {src: 'https://metalevel.tech'}
    src: "https://metalevel.tech"
    [[Prototype]]: HtmlElement
        click: ƒ ()
        render: ƒ ()
        constructor: ƒ HtmlImageElement(src = '#')
        [[Prototype]]: Object
            focus: ƒ ()
            constructor: ƒ HtmlElement()
            [[Prototype]]: Object
img.click();
img.focus();
clicked
focused
console.log(img.render());
<img src="https://metalevel.tech" />

3. Fi­nal­ly cre­ate an ar­ray of two or more HTML Se­lect and Im­age el­e­ments and print to the con­sole the out­put of the .ren­der() method of each of them.

const elements = [
    new HtmlSelectElement(['1', '2', '3']), 
    new HtmlImageElement('https://szs.space')
];
for (let element of elements)
    console.log(element.render());
<select>
	<option>1</option>
	<option>2</option>
	<option>3</option>
</select>
<img src="https://szs.space" />

Cre­ate Stack with ES6 Class­es

By us­ing ES6 Class­es, im­ple­ment a LI­FO Stack that has the fol­low­ing mem­bers:

  • push() add el­e­ment to the stack,
  • pop() re­move el­e­ment from the stack,
  • peek() show the el­e­ment on top,
  • count re­turn the length of the stack,
  • show print the stack.
const _items = new WeakMap();
class Stack {
    constructor() {
        _items.set(this, []);
    }

    get show() { return _items.get(this); }
    get count() { return _items.get(this).length; }
    
    push(obj) {
        const items = _items.get(this);
        if (obj === undefined) throw new Error('Invalid value.');
        items.push(obj);
    }
    
    pop() {
        const items = _items.get(this);
        if (items.length === 0) throw new Error('Stack is empty.');
        return items.pop();
    }

    peek() {
        const items = _items.get(this);
        if (items.length === 0) throw new Error('Stack is empty.');
        return items[items.length - 1];
    }
}
const stack = new Stack();
stack;
Stack {}
    count: 0
    show: Array(0)
    [[Prototype]]: Object
        constructor: class Stack
        count: 0
        show: Array(0)
        get count: ƒ count()
        get show: ƒ show()
        peek: ƒ peek()
        pop: ƒ pop()
        push: ƒ push(obj)
        [[Prototype]]: Object
stack.push('a');
stack.push('b');
stack.push('c');
stack.count;
3
stack.pop();
'c'
stack.peek();
'b'
stack.show;
(2) ['a', 'b']