sota7green
sota7green

Reputation: 23

Issues with closures and "this" keyword in JavaScript

I am trying to create a simple JavaScript Calculator. However, when I run my code and make a change to any of the three fields (value box or sign) I get an error stating

this.calculate is not a function

The error comes up under the this.calculate for whichever prototype function is being called depending on if you change your numerical value or the formula sign.

I've read up on closures, callbacks and the "this" keyword and I believe the issue lies somewhere there with how I am calling my prototype functions. Would someone help explain where in my code I am making a mistake and how to resolve it?

Here is the JavaScript

    var Formula=function() {
    this.value1 = null;
    this.value2 = null;
    this.sign = null;
    this.result = null;
};

Formula.prototype.calculate = function() {
    switch (this.sign) {
    case '+':
        this.result = this.value1 + this.value2;
        break;
    case '-':
        this.result = this.value1 - this.value2;
        break;
    case '/':
        this.result = this.value1 / this.value2;
        break;
    case '*':
        this.result = this.value1 * this.value2;
        break;

    default:
        break;
    } 
    document.querySelector('#result').innerHTML = this.result;
};

Formula.prototype.updateValue = function(event) {
    if (event.currentTarget.id === '#value1')
        this.value1 = parseFloat( event.currentTarget.value );
    else this.value2 = parseFloat( event.currentTarget.value );
    this.calculate();
};

Formula.prototype.updateSign = function(event) {
    this.sign = event.currentTarget.value;
    this.calculate();
};

document.addEventListener('DOMContentLoaded', function () {
(function() {
    var equation = new Formula();
    document.querySelector('#sign').addEventListener('change', equation.updateSign);
    var values = document.querySelectorAll('.value');
    for (var i = 0, numValues = values.length; i < numValues; i++) {
        values[i].addEventListener('change', equation.updateValue);
    }
})();
});

And here is the HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="calcJS.js"></script>
</head>
<body>
<input type="number" class="value" id="value1"/>
<select id="sign">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="*">*</option>
    <option value="/">/</option>
</select>
<input type="number" class="value" id="value2"/> 
= 
<span id="result"/>
</body>
</html>

Upvotes: 2

Views: 242

Answers (2)

adeneo
adeneo

Reputation: 318182

The this value in a function depends on how the function is called.

Generally when you do

var equation = new Formula();

equation.updateSign();

this would be the "Formula object" inside updateSign

However, when you do this

var equation = new Formula();
document.querySelector('#sign').addEventListener('change', equation.updateSign);

you're referencing the function, not calling it, the event handler eventually calls it, setting the this value to the changed element, not the equation object

If you want this to be the object instead, you'd have to do something like

var equation = new Formula();
document.querySelector('#sign').addEventListener('change', function() {
    equation.updateSign(arguments);
});

or using bind to return a new function with a set this value

var equation = new Formula();
document.querySelector('#sign').addEventListener('change', equation.updateSign.bind(equation));

FIDDLE

You also had a logical flaw

if (event.currentTarget.id === '#value1')

The ID is always returned without the hash

if (event.currentTarget.id === 'value1')

Upvotes: 2

Jesse Squire
Jesse Squire

Reputation: 7745

The issue that you're seeing is due to the way that event handling works. When an event handler is called, this is implicitly passed to the handler function as the element that is the target of the event.

In order to do keep the original this scope, you'll need to wrap your function and invoke it directly. For example:

var equation = new Formula();

document.querySelector('#sign').addEventListener('change', function(event) 
{ 
    equation.updateSign(event); 
});

This Fiddle helps to illustrate. Hope that helps.

Upvotes: 0

Related Questions