Functions redux (part 2)

Scope

Refers to where can we use variables

Variables are introduced with let, const, and function arguments

const width = 100;
const height = 100;

const drawFoo = (r) => {
  const x = Math.floor(Math.random() * width);
  const y = Math.floor(Math.random() * height);
  for (let i = 0; i < 10; i++) {
    drawCircle(x + i, y + i, r);
  }
}

Global scope

Variables defined at the top level of the file are in global scope. In this code, width, height, and drawFoo.

Function scope

Function arguments and variables defined at the top level of the function are in the scope defined by the function. In this code, r, x, and y.

Loop scope

A for loop creates a new scope for its loop variable that covers the body of the loop.

All three scopes

At any point in the code we can only refer to variables defined in the scopes that enclose that place in the code.

And every block defines a scope

const foo = (c1, c2) => {
  if (c2) {
    let i = 10;
    if (c2) {
      let i = 20;
      { // this block just introduces a new scope
        let i = 30;
        console.log(i);
      }
      console.log(i);
    }
    console.log(i);
  }
}

Each let i creates a new variable in the smallest scope containing it which “shadows” any i variables from enclosing scopes.

Outputs

» foo(true, true)
30
20
10

Versus

const bar = (c1, c2) => {
  if (c2) {
    let i = 10;
    if (c2) {
      i = 20;
      {
        i = 30;
        console.log(i);
      }
      console.log(i);
    }
    console.log(i);
  }
}

Same scopes but assigning new values to i variable from the outer scope rather than creating new variables.

This outputs

» bar(true, true)
30
30
30

Why to limit the scope of variables

Mystery #1

const mysteryOne = (a, b) => {
  return a + b * 2;
}

What does this function call evaluate to?

mysteryOne(10, 3)

16. Not very mysterious.

Everything we need to understand about this function is defined in the scope of the function.

Mystery #2

const mysteryTwo = (a) => {
  return a + b * 2;
}

What does this function call evaluate to?

mysteryTwo(10)

Dunno. We can’t understand this function in isolation. We need to see how b is defined and where it changes.

I.e. we need to look outside the scope of the function to understand it.

This isn’t always so bad

Suppose we look in the global scope and find this:

const b = 42;

Maybe b isn’t the best name but now we now know everything we need to know to understand the behavior of mystery2.

On the other hand

Suppose we look in the global scope and find this:

let b = 42;

Oh no.

Everything is still pretty much a mystery. We have to look at all the code in our program to see when b changes in order to fully understand mystery2.

Function rules of thumb

  • Keep functions short (~5 lines is good)

  • Prefer taking arguments over referencing global variables

  • Prefer returning values rather than setting global variables

  • Functions that do need to make changes to global variables should be higher-level functions.

That is, prefer this …

let score = 0;

const updateScore = (obstacles) => {
  score += obstacles * 10;
  score += bonusPoints(obstacles);
}

const bonusPoints = (obstacles) => {
  let bonus = 0;
  // some complicated computation using obstacles
  return bonus;
}

Because updateScore calls bonusPoints, it is a higher-level function.

To this …

let score = 0;

const updateScore = (obstacles) => {
  score += obstacles * 10;
  updateBonusPoints(obstacles);
}

const updateBonusPoints = (obstacles) => {
  let bonus = 0;
  // some complicated computation using obstacles
  score += bonus;
}

The updateBonusPoints function is less useful than bonusPoints from the previous slide. And it also makes updateScore harder to read.