30 Days into JS - Day 1

·

9 min read

Create Hello World Function

  • Write a function createHelloWorld. It should return a new function that always returns "Hello World".

  • Function Syntax

    In JavaScript, there are two main ways to declare a function. One of which is to use the function keyword.

    • Basic Syntax

The syntax is:

    function f(a, b) {
        const sum = a + b;
        return sum;
    }
    console.log(f(3, 4)); // 7

In this example, f is the name of the function. (a, b) are the arguments. You can write any logic in the body and finally return a result. You are allowed to return nothing, and it will instead implicitly return undefined

  • Anonymous Function

    You can optionally exclude the name of the function after the function keyword.

var f = function(a, b) {
    const sum = a + b;
    return sum;
}
console.log(f(3, 4)); // 7
  • Immediately Invoked Function Expression (IIFE)

    You can create a function and immediately execute it in Javascript.

const result = (function(a, b) {
    const sum = a + b;
    return sum;
})(3, 4);
console.log(result); // 7
  • Why would you write code like this?

    It gives you the opportunity to encapsulate a variable within a new scope. For example, another developer can immediately see that sum can't be used anywhere outside the function body.

  • Functions Within Functions

    A powerful feature of JavaScript is you can actually create functions within other functions and even return them!

function createFunction() {
    function f(a, b) {
        const sum = a + b;
        return sum;
    }
    return f;
}
const f = createFunction();
console.log(f(3, 4)); // 7

In this example, createFunction() returns a new function. Then that function can be used as normal.

  • Function Hoisting

    JavaScript has a feature called hoisting where a function can sometimes be used before it is initialized. You can only do this if you declare functions with the function syntax.

function createFunction() {
    return f;
    function f(a, b) {
        const sum = a + b;
        return sum;
    }
}
const f = createFunction();
console.log(f(3, 4)); // 7

In this example, the function is returned before it is initialized. Although it is valid syntax, it is sometimes considered bad practice as it can reduce readability.

  • Closures

    When a function is created, it has access to a reference to all the variables declared around it, also known as it's lexical environment. The combination of the function and its environment is called a closure. This is a powerful and often used feature of the language.

function createAdder(a) {
    function f(b) {
        const sum = a + b;
        return sum;
    }
    return f;
}
const f = createAdder(3);
console.log(f(4)); // 7

In this example, createAdder passes the first parameter a and the inner function has access to it. This way, createAdder serves as a factory of new functions, with each returned function having different behavior.

  • Arrow Syntax

    The other common way to declare functions is with arrow syntax.

    Basic Syntax
      const f = (a, b) => {
          const sum = a + b;
          return sum;
      };
      console.log(f(3, 4)); // 7
    
    Omit Return

    If you can write the code in a single line, you can omit the return keyword. This can result in very short code.

const f = (a, b) => a + b;
console.log(f(3, 4)); // 7
Differences

There are 3 major differences between arrow syntax and function syntax.

  1. More minimalistic syntax. This is especially true for anonymous functions and single-line functions. For this reason, this way is generally preferred when passing short anonymous functions to other functions.

  2. No automatic hoisting. You are only allowed to use the function after it was declared. This is generally considered a good thing for readability.

  3. Can't be bound to this, super, and arguments or be used as a constructor. These are all complex topics in themselves but the basic takeaway should be that arrow functions are simpler in their feature set.

  • Rest Arguments

    You can use rest syntax to access all the passed arguments as an array. This isn't necessary for this problem, but it will be a critical concept for many problems.

    Basic Syntax

    The syntax is:

function f(...args) {
    const sum = args[0] + args[1];
    return sum;
}
console.log(f(3, 4)); // 7

In this example the variable args is [3, 4].

  • Why

It may not be immediately obvious why you would use this syntax because you can always just pass an array and get the same result.

The primary use-case is for creating generic factory functions that accept any function as input and return a new version of the function with some specific modification.

By the way, a function that accepts a function and/or returns a function is called a higher-order function, and they are very common in JavaScript.

function log(inputFunction) {
    return function(...args) {
        console.log("Input", args);
        const result = inputFunction(...args);
        console.log("Output", result);
        return result;
    }
}
const f = log((a, b) => a + b);
f(1, 2); // Logs: Input [1, 2] Output 3

Solutions to Problem

  1. Function Syntax

var createHelloWorld = function() {
    return function() {
        return "Hello World";
    }
};
  1. Arrow Syntax

var createHelloWorld = function() {
    return () => "Hello World";
};
  1. Arrow Syntax + Rest Arguments

var createHelloWorld = function() {
    return (...args) => "Hello World";
};

Counter

Given an integer n, return a counter function. This counter function initially returns n and then returns 1 more than the previous value every subsequent time it is called (n, n + 1, n + 2, etc).

  • Closure Example

In Javascript, you can declare functions within other functions and return them. The inner function has access to any variables declared above it.

function createAdder(a) {
  return function add(b) {
    const sum = a + b;
    return sum;
  }
}
const addTo2 = createAdder(2);
addTo2(5); // 7

The inner function add has access to a. This allows the outer function to serve as a factory of new functions, each with different behavior.

  • Closures Versus Classes

You may notice that in the above example createAdder is very similar to a class constructor.

class Adder {
  constructor(a) {
     this.a = a;
  }

  add(b) {
    const sum = this.a + b;
    return sum;
  }
}
const addTo2 = new Adder(2);
addTo2.add(5); // 7

Besides differences in syntax, both code examples essentially serve the same purpose. They both allow you to pass in some state in a "constructor" and have "methods" that access this state.

One key difference is that closures allow for true encapsulation. In the class example, there is nothing stopping you from writing addTo2.a = 3; and breaking it's expected behavior. However, in the closure example, it is theoretically impossible to access a. Note that as of 2022, true encapsulation is achievable in classes with # prefix syntax.

Another difference is how the functions are stored in memory. If you create many instances of a class, each instance stores a single reference to the prototype object where all the methods are stored. Whereas for closures, all the "methods" are generated and a "copy" of each is stored in memory each time the outer function is called. For this reason, classes can be more efficient, particularly in the case where there are many methods.

Unlike in languages like Java, you will tend to see code written with functions rather than with classes. But since JavaScript is a multi-paradigm language, it will depend on the particular project you are working on.

Approach 1: Increment Then Return

We declare a variable currentCount and set it equal to n - 1. Then inside the counter function, increment currentCount and return the value. Note that since currentCount is modified, it should be declared with let rather than const.

var createCounter = function(n) {
  let currentCount = n - 1;
  return function() {
    currentCount += 1;
    return currentCount;      
  };
};

Approach 2: Postfix Increment Syntax

JavaScript provides convenient syntax that returns a value and then increments it. This allows us to avoid having to initially set a variable to n - 1.

var createCounter = function(n) {
  return function() {
    return n++;      
  };
};

Approach 3: Prefix Decrement and Increment Syntax

JavaScript also has syntax that allows you to increment a value and then return it. Because the increment happens before the value is returned, we must first decrement the value initially similar to Approach 1.

var createCounter = function(n) {
  --n;
  return function() {
    return ++n;      
  };
};

Approach 4: Postfix Increment Syntax With Arrow Function

We can reduce the amount of code in Approach 2 by using an arrow function with an implicit return.

var createCounter = function(n) {
  return () => { return n++; };
};

To Be Or Not To Be

Write a function expect that helps developers test their code. It should take in any value val and return an object with the following two functions.

  • toBe(val) accepts another value and returns true if the two values === each other. If they are not equal, it should throw an error "Not Equal".

notToBe(val) accepts another value and returns true if the two values !== each other. If they are equal, it should throw an error "Equal".

/**
 * @param {any} val
 * @return {Object}
 */
var expect = function(val) {
  return {
    toBe: function(otherVal) {
      if (val === otherVal) {
        return true;
      } else {
        throw new Error("Not Equal");
      }
    },
    notToBe: function(otherVal) {
      if (val !== otherVal) {
        return true;
      } else {
        throw new Error("Equal");
      }
    }
  };
};

Counter II

Write a function createCounter. It should accept an initial integer init. It should return an object with three functions.

The three functions are:

  • increment() increases the current value by 1 and then returns it.

  • decrement() reduces the current value by 1 and then returns it.

  • reset() sets the current value to init and then returns it.

/**
 * @param {integer} init
 * @return { increment: Function, decrement: Function, reset: Function }
 */
var createCounter = function(init) {
    let present = init;
    return {
        increment:()=> ++present,
        decrement:()=> --present,
        reset:()=> present = init,
    }
};

/**
 * const counter = createCounter(5)
 * counter.increment(); // 6
 * counter.reset(); // 5
 * counter.decrement(); // 4
 */

Apply Transform Over Each Element in Array

Given an integer array arr and a mapping function fn, return a new array with a transformation applied to each element.

The returned array should be created such that returnedArray[i] = fn(arr[i], i).

Please solve it without the built-in Array.map method.

var map = function(arr, fn) {
  const transformedArr = [];
  for (let i = 0; i < arr.length; i++) {
    transformedArr[i] = fn(arr[i], i);
  }
  return transformedArr;
};