Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Functional programming JavaScript: best way to incorporate an array into functions?

I am building a calculator and I have the below array outside of the function and then the function mutates it, which I understand is poor coding practise (not functional).

How would I incorporate the array into the function, keep in mind there will be other functions (plus, minus etc) which also need to access the array and updated data within it.

let arr = []; // Store user input.
let str = ''; // Display user input in the display section.

decimal.addEventListener('click', () => {
  arr.push('.');
  str = str.concat('.');
  display.value = str;
});

plus.addEventListener('click', () => {
  arr.push('+');
  str = str.concat('+');
  display.value = str;
});

There are more siminlar functions such as plus, 2, 1, 3, 4, 5, 6 etc.

4 Answers

Steven Parker
Steven Parker
231,275 Points

Considering what you use the array for, and that it is a global element, I don't see any issue with the usage shown here. What led you to think it was bad practice? (Maybe I've been doing things wrong myself!)

I've just started learning about functional programming and perhaps i've misunderstood it.

I thought that the idea is to have everything within functions in order to reduce bugs, make it more testable and modular. Also important not to mutate anything in the global scope.

This then made me think perhaps I should be using closures to incorporate the array into a function?

Below is my current JS calculator code which I am planning to rewrite. The below code also fails in to console. with the following message:

"Uncaught TypeError: Cannot read property 'addEventListener' of undefined at loopNumberButtons (scripts.js:47) at scripts.js:136"

/* jshint esversion: 6 */
const display = document.getElementById('display');
const zero = document.getElementById('zero');
const one = document.getElementById('one');
const two = document.getElementById('two');
const three = document.getElementById('three');
const four = document.getElementById('four');
const five = document.getElementById('five');
const six = document.getElementById('six');
const seven = document.getElementById('seven');
const eight = document.getElementById('eight');
const nine = document.getElementById('nine');
const ac = document.getElementById('ac');
const negative = document.getElementById('negative');
const percentage = document.getElementById('percentage');
const divide = document.getElementById('divide');
const multiply = document.getElementById('multiply');
const minus = document.getElementById('minus');
const plus = document.getElementById('plus');
const decimal = document.getElementById('decimal');
const equals = document.getElementById('equals');

const numButtons = [zero, one, two, three, four, five, six, seven, eight, nine];
const operatorArray = [divide, multiply, minus, plus];

let arr = []; // Store user input.
let percentageArr = []; // Array to make percentage function work.
let percentStartNum = []; // the variable number which will be used to work out percentage
let str = ''; // Display user input in the display section.

//This is a loop to avoid recreating the addEventListener for numbers 0 - 9.
function loopNumberButtons() {
  function reduceCode(pushNum, concatNum) { //reduce code function buttons 0-9.
    if (display.value.length < 9) {
      arr.push(pushNum);
    } else {
      alert('Nine digits only.');
      arr.toString().split('').pop().join('');
    }

    ac.innerHTML = 'C';
    str = str.concat(concatNum);
    display.value = str;
  }

  for (let i = 0; i <= numButtons.length; i++) {
    numButtons[i].addEventListener('click', () => { .  //HERE IS WHERE FIRST CONSOLE ERROR SHOWS
      reduceCode(i, i);
    });
  }
}

// function to calculate the arr.
function calculateArray(array) {
  return eval(arr.toString().split(',').join(''));
}

function displayString(string) {
  return str = string;
}

function resetDisplay(str) {
  display.value = str;
}

function loopOperators() {
  for (let i = 0; i < operatorArray.length; i++) {
    operatorArray[i].addEventListener('click', () => {
      displayString(''); //do not delete.
      switch (operatorArray[i]) {
        case divide:
          arr.push('/');
          resetDisplay('');
          break;
        case plus:
          arr.push('+');
          resetDisplay('');
          break;
        case minus:
          arr.push('-');
          resetDisplay('');
          break;
        case multiply:
          arr.push('*');
          percentStartNum.push(display.value);
          resetDisplay('');
          break;
      }
    });
  }
}

negative.addEventListener('click', () => {
  arr.push('-');
  str = str.concat('-');
  display.value = str;
});

decimal.addEventListener('click', () => {
  arr.push('.');
  str = str.concat('.');
  display.value = str;
});

ac.addEventListener('click', () => {
  arr = [];
  percentageArr = [];
  display.value = '0';
  str = '';
  display.style.fontSize = '7rem';
  if (ac.innerHTML = 'C') {
    ac.innerHTML = 'AC';
  }
});

percentage.addEventListener('click', () => { // needs to be fixed
  percentageArr.push('x');
  let number = eval(str);
  display.value = number * 0.01;
});

equals.addEventListener('click', () => {
  str = '';
  console.log(percentageArr.length);
  if (percentageArr.length === 1) {
    display.value = eval(display.value) * percentStartNum[0];

  } else {
    display.value = calculateArray(arr);
    if (display.value.length > 9) {
      display.style.fontSize = '3.5rem';
    }
  }
});
loopOperators();
loopNumberButtons(); //HERE IS WHERE SECOND CONSOLE ERROR SHOWS
Steven Parker
Steven Parker
231,275 Points

If your intent is to create a module and not a whole program, then yes, encapsulation is a good idea. But closures are generally not necessary any more if you make use of the modern (ES2015) syntax extensions.

If you share the HTML portion of the code also, I'll see if I can spot the issue. Even better, make a snapshot of your workspace and post the link to it here.

In the meantime, I spotted this:

    numButtons[i].addEventListener('click', () => { .  //HERE IS WHERE FIRST CONSOLE ERROR SHOWS
//                                                  ^
//                                   this stray period is a syntax error
Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,738 Points

I haven't run your code, so sorry if this is a drive-by opinion. But I noticed that you're using jshint, but you have some issues that jshint is complainng about that are not addressed. Specifically:

  • eval can lead to security issues (this security course will explain why)
  • declaring functions within a loop using the loop variable can lead to confusion and bugs. And there's usually a better way: I'm not sure why it's necessary to loop through all the operators in one big function, and assign different behavior to them depending on the operator. Seems to me each operator could just have it's own event handler that takes care of it's needs.
Steven Parker
Steven Parker
231,275 Points

Array indices go up to but not including the length of the array:

  for (let i = 0; i <= numButtons.length; i++) {  // change "<=" to just "<"

Steven Parker Perfect thanks for the help.

Do you think my logic is okay?

I am trying to program more functionally.

Steven Parker
Steven Parker
231,275 Points

Looks good to me. Considering what the page does, I think the use of global storage is appropriate, but it shouldn't be too hard to encapsulate the whole thing if you really want to.