Notes on Javascript Nested Functions

javascript
Published

December 28, 2024

Notes on Javascript nested functions

Example 1

%%script node
function outside(x) {
    function inside(y) {
      return x + y;
    }
    return inside;
  }
  
  const myfunction = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
  console.log(myfunction(5)); // 8

  // the above is the same as running this:
  console.log(outside(3)(5)); // 8
8
8

Line by line explanation & notes

  • const myfunction = outside(3) > Here we define myfunction variable with outside(3), doing so created a new scope with x = 3. This x is preserved in this scope, ie x inside myfunction scope will always be 3, because we initialized it as myfunction = outside(3).

Each time we initialize outside, a new scope is created. This is similar to how class object works – x here is like an attribute of a class object and myfunction is an object instance – except using nested functions, we can not directly inspect or access x.

  • myfunction(5) > Here we call the variable containing the outside(3) function we initialized. The argument we passed 5 will be passed as y. Since we have initialized x = 3 previously, the inside function will return x + y which is 3 + 5.

  • inside function can access variable defined in outside function. However outside function can not access anything defined in inside function. > This is different from python –> when we defined nested functions, the outer function can access anything that is defined within itself since whatever defined within it is its local scope.

Example 2

%%script node
function outside() {
  const x = 5;
  function inside(x) {
    return x * 2;
  }
  return inside;
}

console.log(outside()(10)); // 20 (instead of 10)
20

Line by line explanation & notes

  • outside()(10) > outside() created a new scope. (10) is where we call inside(10) inside the outside() scope. Therefore x=10 in the inside() function.

  • In nested function, the innermost function takes more precedence than the outside function. > When we call outside()(10), we initialized x=10 within inside() function. Since inside() scope is of higher precedence than outside(), const x = 5 we defined in outside() function scope is overrode.

  • The scope of chain here is inside() ==> outside() ==> global object.

Example 3

%%script node
const getCode = (
  
  // outer function (anonymous function)
  function () {
    const apiCode = "some-api-code"; // A code we do not want outsiders to be able to modify…

    // nested inner function (anonymous function)
    return function () {
      return apiCode + '--some-other-code-added-in-inner-function';
    };
      
  })();

console.log(getCode()); 
some-api-code--some-other-code-added-in-inner-function

Line by line explanation & notes

  • On line 5, we define outer function here.
function () {
    const apiCode = "some-api-code";
    ...
}
  • On line 9 - line 11, we define another function within the outer function.
return function () {
      return apiCode + '--some-other-code-added-in-inner-function';
    };
  • (); on line 13 immediately invoke the outer function. The outer function then assign its return value (the inner function) to getCode.

Example 4

%%script node
const getCode = (
  
  // outer function (anonymous function)
  function () {
    const apiCode = "some-api-code"; // A code we do not want outsiders to be able to modify…

    // nested inner function (anonymous function)
    return function () {
      return apiCode + '--some-other-code-added-in-inner-function';
    };
      
  })();

console.log(getCode); // here we just save the outer function in `getCode` variable but did not call it
[Function (anonymous)]

Example 5

%%script node
const getCode = function () {
    const apiCode = "some-api-code"; 
    console.log("this is from outer function");
    return function () {
        console.log("this is from inner function");
        return apiCode + '--some-other-code-added-in-inner-function';
    };
};


const myfunct = getCode(); // calls the outer function

console.log('-------')
console.log(myfunct); // we saved the outer function in this variable - outer function contains the inner function

console.log('-------')
console.log(myfunct()); // explicitly call the inner function
this is from outer function
-------
[Function (anonymous)]
-------
this is from inner function
some-api-code--some-other-code-added-in-inner-function
  • If we don’t directly invoke the outer function at end of outer function definition, we would have to explicitly call the outer function. See example above.

Example 6: Multiple Arguments

%%script node
function myConcat(separator) {
  let result = ""; // initialize list
  // iterate through arguments
  for (let i = 1; i < arguments.length; i++) {
    result += arguments[i] + separator;
  }
  return result;
}

console.log(myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley")); // "sage. basil. oregano. pepper. parsley. "
sage. basil. oregano. pepper. parsley. 

Line by line explanation & notes

  • Within the function, arguments are maintained like an array and can be accessed like so arguments[i];. Where i is the index of the arguments, starting at 0.
  • This means, you can pass any number of arguments when you call the function. Eg above myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");. Note that in the function definition we only specify 1 parameter myConcat(separator). This is different than python where we need to have to pass the same number of arguments as we defined in the function definition.

Example 7: Object with methods

%%script node

const myCalc = function(val1) {

    return {
        // anonymous function here
        add: function(val2) {
            return val1 + val2
        },

        subtract: function(val2) {
            return val1 - val2
        }

    }
};

console.log('Example 1 -------------- ')
calc1 = myCalc(10);
console.log(calc1.add(4)); // 10 + 4 = 14
console.log(calc1.subtract(1)); // 10 - 1 = 9

console.log('Example 2 -------------- ')
calc2 = myCalc(5);
console.log(calc2.add(2)); // 5 + 2 = 7
console.log(calc2.subtract(3)); // 5 - 3 = 2
Example 1 -------------- 
14
9
Example 2 -------------- 
7
2
  • At a glance, the above looks like a function containing a dictionary of functions (using python terminology). In Javascript, this is called object with methods.
  • calc1 = myCalc(10); Here we initialized calc1 with outer function scope of val1 = 10.
  • Notice we can directly call the inner functions using dot notation, similar to how we would do to class object. This is possible because we returned the object return {add: function() {...}, subtract: function() {...} }.
  • In the example above, we use anonymous function. The syntax is:
function outer_func() {

    return {

        inner_func1: function() {
            return ...;
        },

        inner_func2: function() {
            return ...;
        }

    }

}
%%script node

const myCalc = function(val1) {

    // named function here
    function add(val2) {
            return val1 + val2
    }

    function subtract(val2) {
        return val1 - val2
    }

    return {add, subtract};
};

console.log('Example 1 -------------- ')
calc1 = myCalc(10);
console.log(calc1.add(4)); // 10 + 4 = 14
console.log(calc1.subtract(1)); // 10 - 1 = 9

console.log('Example 2 -------------- ')
calc2 = myCalc(5);
console.log(calc2.add(2)); // 5 + 2 = 7
console.log(calc2.subtract(3)); // 5 - 3 = 2
Example 1 -------------- 
14
9
Example 2 -------------- 
7
2
  • Above is another way to return the same methods. Here we use named functions. The syntax is:
function outer_func() {

    function inner_func1() {
        return ...;
    }

    function inner_func2() {
        return ...;
    }

    return {inner_func1, inner_func2}; // main difference is here

}

Notice that we directly define the named functions, then return {inner_func1, inner_funct2}. This is different than the previous example where we directly define the inner functions in the return statement return {inner_func1: ... , inner_func2: ... }

Example 8

%%script node
var createCounter = function(init) {

    let cur = init;

    return {
        increment: function() {
            return ++cur; // increment cur then return
        },

        decrement: function() {
            return --cur ; // decrement cur then return
        },

        reset: function() {
            return (cur = init); // reassign cur then return
        }
    }
};

const counter = createCounter(5); // init = 5
console.log(counter.increment()); // 6
console.log(counter.reset()); // 5
console.log(counter.decrement()); // 4
 
6
5
4
  • init value never change after initialization. On line 21, we initialized counter = createCounter(5), this means init = 5.
  • On line 4, we created another variable cur, which we store the initial init value.
  • In each inner functions, we perform ++cur (increment cur then return cur); --cur (decrement cur then return cur);
  • On line 16, return (cur = init). Here we are essentially doing 2 steps, reassigning cur = init, then return cur. We can explicitly write it in 2 steps like below:
reset: function() {
    cur = init;
    return cur;
}
  • Likewise, we can also expand return ++cur where we increment cur, then returns it into 2 separate lines, like below:
increment: function() {
    ++cur; // or cur++, it does not matter here. See below example for details
    return cur;
}

Postfix vs Prefix

%%script node
let cur = 5;
let result = ++cur;  // `cur` is incremented first, then `result` gets the new value of `cur`
console.log(result);  // 6 (value of `cur` after increment)
console.log(cur);     // 6 (value of `cur` after increment)
6
6
%%script node
let cur = 5;
let result = cur++;  // result gets the current value of `cur`, then `cur` is incremented
console.log(result);  // 5 (value of `cur` before increment)
console.log(cur);     // 6 (value of `cur` after increment)
5
6

References

  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#nested_functions_and_closures