Home Chapter 8: Functions.
Post
Cancel

Chapter 8: Functions.

Introduction to Functions

A function is a block of Javascript code that is defined once but may be executed, or invoked, any number of times. A function may optionally accept arguments, and may optionally return a value. Defining a function does not execute it. Defining it names the function and specifies what to do when the function is invoked. Calling the function causes the code inside the function body to be executed.

Key Takeaways

  • Functions are the fundamental modular unit of JavaScript. They are used for code reuse, information hiding, and composition.
  • Functions are used to specify the behavior of objects. Functions are the executable units of the language.
  • They are also values in the language. Functions are objects, instances of the Function type.
  • Functions can be stored in variables, objects, and arrays.
  • Functions can be passed as arguments to functions, and functions can be returned from functions.
  • Functions can also possess properties that can be dynamically created and assigned.
  • JavaScript’s functions are parameterized: a function definition may include a list of identifiers, known as parameters, that work as local variables for the body of the function. Function invocations provide values, or arguments, for the function’s parameters. Functions often use their argument values to compute a return value that becomes the value of the function-invocation expression. In addition to the arguments, each invocation has another value—the invocation context—that is the value of this keyword.
  • If assigned to a property of an object, a function is called a method. When a function is invoked as a method, the function body can access the object through the this keyword.
  • JavaScript’s functions are closures: they are defined in a scope that includes variables that are in scope where the function is defined.

Defining Functions

👉 Function Declarations

A function definition (also called a function declaration, or function statement) consists of the function keyword followed by:

  • The name of the function.
  • A list of arguments to the function, enclosed in parentheses and separated by commas. These arguments are the names of the function’s parameters. The arguments are not required, and the parentheses can be omitted if the function has no parameters.
  • The JavaScript statements that define the function, enclosed in curly braces, {...}.
  • An optional return statement to explicitly specify the value returned by the function. If omitted, the function returns undefined.
1
2
3
4
// This function adds two numbers and returns the sum of them.
function add(x, y) {
    return x + y;
}

📓 Function definitions can appear anywhere in JavaScript code. They are hoisted (see Chapter 3) to the top of the scope in which they are defined.

👉 Function Expressions

A function expression defines a function as a part of a larger expression syntax (typically a variable assignment ).

1
2
  // This function returns the value of its argument.
  let square = function(x) { return x*x; }

The function keyword can also be used as a prefix to a function expression to make it a function statement. Here, the name of the function is optional, and the function can be invoked using the name of the variable that it is assigned to.

The downside of this approach is that the function name is not available within the function body, which makes some forms of recursion impossible.

1
2
// This function returns the value of its argument.
let square = function(x) { return x*x; }

📓 Function expressions are not hoisted, and so they cannot be invoked before they are defined.

👉 Arrow Functions

Arrow functions are a concise notation for defining function expressions. They are also sometimes called lambda expressions, after the notation used in the lambda calculus to define function expressions. Arrow functions are a syntactically compact alternative to a regular function expression, although they are limited in their flexibility.

1
2
// This function returns the value of its argument.
let square = x => x*x;

If you need to specify multiple parameters or a function body, you’ll need to enclose the arrow function in curly braces and supply a return statement.

1
2
// This function returns the sum of its arguments.
let add = (a, b) => { return a + b; }

If no arguments are needed, you must include an empty pair of parentheses.

1
2
// This function returns the value 1.
let f = () => 1;

Also, if the body of your arrow function is a single return statement but the expression to be returned is an object literal, you must surround the object literal with parentheses so that the JavaScript interpreter doesn’t mistake it for the function body.

1
2
// This function returns an object literal.
let getTempItem = id => ({ id: id, name: "Temp" });

These functions are helpful when passing a function as an argument to another function, eg. map().

1
2
let arr = [1, 2, 3, 4, 5];
arr = arr.map(v => v * 2); // [2, 4, 6, 8, 10]

Arrow functions differ from functions defined in other ways in one critical way: they inherit the value of the this keyword from the environment in which they are defined rather than defining their own invocation context as functions defined in other ways do. This is an important and very useful feature of arrow functions. Arrow functions also differ from other functions in that they do not have a prototype property, which means that they cannot be used as constructor functions for new classes.

👉 Nested Functions

JavaScript allows you to define functions inside other functions. Such nested functions are visible only to other code inside the functions in which they are defined. This is a powerful feature of JavaScript that is used quite heavily in object-oriented programming and in asynchronous event handling, but nested functions are useful even if you never explicitly use this feature.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function CircularObject(diameter) {
  const attributes = new Object({
    diameter,
    radius: diameter/2,
    circumference() {
      return Math.ceil(Math.PI * this.diameter)
    },
    area() {
      return Math.ceil(Math.PI * (this.radius ** 2))
    },
    toString() { // overriding the default toString() method
      return `Radius: ${this.radius}\nDiameter: ${this.diameter}\nCircumference: ${this.circumference()}\nArea: ${this.area()}`
    },
    valueOf() { // overriding the default valueOf() method
      return this.radius;
    }
  })

  // Nested function
  const PrintData = obj => obj.toString() // obj.valueOf() is called implicitly
  console.log(PrintData(attributes))
}

CircularObject(14); // Radius: 7 Diameter: 14 Circumference: 44 Area: 154

📓 Nested functions are not bound to the object for which they are defined. They are simply local variables of the function in which they are defined.

Invoking Functions

👉 Function Invocation

A function invocation is typically a standalone statement or expression that invokes (that is, calls) the function.

1
2
3
function square(x) { return x*x; } // A function to compute the square of a value.

console.log(square(5)); // => 25

👉 Method Invocation

A method is nothing more than a JavaScript function that is stored in a property of an object. If you have a function f and an object o, you can define a method named m of o with the following line:

1
o.m = f;

Using the example from the nested functions section, we can define a method named area of attributes object with the following line:

1
attributes.area() // => 154

You can also use the square brackets to call a method:

1
attributes['area']() // => 154

When a function is invoked as a method, the value of the this keyword is bound to that object for the duration of that function call.

:notebook: In case you nest a function inside a method, the this keyword will be bound to the global object. To avoid this, use an arrow function or the bind() method.

You can invoke the bind() method of the nested function to define a new function that is implicitly invoked on a specified object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Nesting functions inside objects, and invoking them using the bind() method to bind the this keyword to the object
const Person = new Object({
  firstName : "Frank",
  lastName : "Lucas",
  set NameFirst(firstName) {
    this.firstName = firstName
  },
  set NameLast(lastName) {
    this.lastName = lastName
  },
  get NameLast() {
    return this.lastName
  },
  get NameFirst() {
    return this.firstName
  },
  getNames() { // Method
    const dataRefine = (function() { // Nested function
      return `Name: ${this.NameFirst} ${this.NameLast}.`
    }).bind(this); // bind() method is used to bind the this keyword to the object

    return dataRefine(); // Invoking the nested function
  }
})

console.log(Person.getNames()); 

/*
Output:
Name: Frank Lucas.
 */

👉 Constructor Invocation

JavaScript is a prototype-based object-oriented language. This means that every object is linked to another object that acts as its prototype. When you make a new object, you can select its prototype. The mechanism that JavaScript provides to do this is messy and complex, but it can be significantly simplified.

When a function is invoked with the new keyword, then it is called as a constructor function.

1
2
3
4
5
6
7
8
9
function Circle(radius) {
  this.radius = radius;
  this.area = function() {
    return Math.ceil(Math.PI * (this.radius ** 2))
  }
}

const circle = new Circle(14);
console.log(circle.area()); // => 615

When a function is invoked with the new keyword, then a new object is created with a hidden link to the value of the function’s prototype member,

👉 Indirect Invocation

JavaScript functions are objects and like all JavaScript objects, they have methods. Two of these methods, call() and apply(),

call() method invokes the function with a specified this value and arguments provided individually.

1
2
3
4
5
6
7
function sum(a, b) {
  return a + b;
}

// Invoking the sum() function using the call() method. The first argument is the object to be bound to the this keyword, 
// and the rest are the arguments to be passed to the function.
console.log(sum.call(null, 1, 2)); // => 3

apply() method invokes the function with a specified this value and arguments provided as an array.

1
2
3
4
5
6
7
function sum(a, b) {
  return a + b;
}

// Invoking the sum() function using the apply() method. The first argument is the object to be bound to the this keyword,
// and the second argument is an array of arguments to be passed to the function.
console.log(sum.apply(null, [1, 2])); // => 3

👉 Implicit Function Invocation

When a function is not the property of an object, then it is invoked as a function:

1
2
3
function square(x) { return x*x; } // A simple function to return the square of a value.

console.log(square(5)); // => 25

When a function is invoked with no explicit base object, then the base object is the global object.

1
2
3
const square = function(x) { return x*x; } // Function expression assigned to a variable.

console.log(square(5)); // => 25

Implicit function invocation is a common source of programming errors in JavaScript. They should be avoided.

Instead of using implicit function invocation, use the call() or apply() method to explicitly specify the base object for invocation.

The language features that can cause implicit invocation are:

  • If an object has getter or setter methods defined, then it is invoked implicitly when you attempt to get or set the value of a property.
  • When an object is used in a string or regular expression context, toString() method is invoked implicitly.
  • When an object is used in a numeric context, valueOf() method is invoked implicitly.
  • When you loop over elements of an iterable object, the iterator() method is invoked implicitly.
  • A tagged template literal invokes the tag() method of the tag function implicitly.

Function Arguments and Parameters

Arguments are the values that are passed to a function when it is invoked. Parameters are the variables that are used to store the arguments passed to a function.

👉 Default Parameters

You can specify default values for parameters in a function definition. If you do so, then the default values are used when the corresponding arguments are omitted. They enable us to write functions with optional and fewer parameters.

1
2
3
4
5
  function sum(a, b = 5) {
    return a + b;
  }

  console.log(sum(1)); // => 6 - b is omitted, so it takes the default value of 5

When designing a function, you should specify the default values for parameters at the end of the parameter list.

👉 Rest Parameters

A rest parameter is prefixed with three dots (…) and must be the last parameter in a function definition. Functions like this that accept any number of arguments are called variadic functions/vararg methods/variadic-arity functions.

Ir is used to store the remaining arguments passed to the function. It allows us to represent an indefinite number of arguments as an array.

1
2
3
4
5
6
7
8
9
10
11
 // Check the minimum value
const Minimum = (minimum = Infinity, ...others) => {
  let minValue = minimum; // Set the minimum value to the first argument
  // Loop through the values in the array on the rest
  for(const val of others)
    if(minValue > val) // If the current value is less than the minimum value
      minValue = val // Set the minimum value to the current value
  return minValue // Return the minimum value
}

console.log(Minimum(20, -585, 104,545454, 0, -444)); // => -585

👉 Spread Operator

You can use the spread operator to spread the elements of an array as arguments to a function. It is used to unpack the elements of an array and pass them as arguments to a function.

1
2
3
4
5
6
7
  function sum(a, b, c) {
    return a + b + c;
  }

  const numbers = [1, 2, 3];

  console.log(sum(...numbers)); // => 6

👉 Function Arguments Object

Every function has a special local variable named arguments, which is an array-like object that contains the values of the arguments passed to the function. It is not an array, but it is array-like. This was used before the introduction of rest parameters in ES6.

1
2
3
4
5
6
7
8
9
  function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
      total += arguments[i];
    }
    return total;
  }

  console.log(sum(1, 2, 3)); // => 6

Destructuring function arguments into parameters

Destructuring is a convenient way of extracting multiple values from data stored in (possibly nested) objects and Arrays. It can be used in locations that receive data (such as the left-hand side of an assignment).

1
2
3
4
5
6
7
  function sum({ a, b, c }) {
    return a + b + c;
  }

  const numbers = { a: 1, b: 2, c: 3 };

  console.log(sum(numbers)); // => 6

Parameters as array literals. If you define a function that has parameter names within square brackets, you are telling the function to expect an array value to be passed for each pair of square brackets. As part of the invocation process, the array arguments will be unpacked into the individually named parameters.

1
2
3
4
5
6
//params as array literals
function arrParams([x1, y1], [x2, y2]) {
  return [x1 + x2, y1 + y2];
}

console.log(arrParams([4,5],[6,7])); // => [10,12]

Similarly, if you are defining a function that expects an object argument, you can destructure parameters of that object.

1
2
3
4
5
6
//params as object literals
function objParams({x1, y1}, {x2, y2}) {
  return [x1 + x2, y1 + y2];
}

console.log(objParams({x1: 4, y1: 5}, {x2: 6, y2: 7})); // => [10,12]

🌟When destructuring properties with one name into parameters with different names, remember that the property names go on the left and the parameter names go on the right.

1
2
3
4
5
6
//params as object literals
function objParams({x: x1, y: y1}, {x: x2, y: y2}) {
    return {x: x1 + x2, y: y1 + y2};
}

console.log(objParams({x: 4, y: 5}, {x: 6, y: 7})); // => {x: 10, y: 12}

🌟 You can also define parameter defaults with destructured parameters.

1
2
3
4
5
6
//params as object literals
// Multiply the vector {x,y} or {x,y,z} by a scalar value
function vectorMultiply({x, y, z=0}, scalar) {
    return { x: x*scalar, y: y*scalar, z: z*scalar };
}
vectorMultiply({x: 1, y: 2}, 2) // => {x: 2, y: 4, z: 0}

🌟✳ NB When there are many optional arguments or when the parameter list is long enough that it is hard to remember the correct order of assignment. In such cases, it is better to pass an object literal as an argument and destructure it into parameters.

1
2
3
4
5
6
7
8
9
//params as object literals
function arraycopy({from, to=from, n=from.length, fromIndex=0, toIndex=0}) {
  let valuesToCopy = from.slice(fromIndex, fromIndex + n);
  to.splice(toIndex, 0, ...valuesToCopy);
  return to;
}
let a = [1,2,3,4,5], b = [9,8,7,6,5];
// You can call arraycopy() with named arguments, in any order or combination:
arraycopy({from: a, n: 3, to: b, toIndex: 4}) // =>[9,8,7,6,1,2,3,5]

When you destructure an array, you can define a rest parameter for extra values within the array that is being unpacked. That rest parameter within the square brackets is completely different from the rest parameter for the function as seen ⬇:

1
2
3
4
5
6
7
8
// This function expects an array argument. The first two elements of that
// array are unpacked into the x and y parameters. Any remaining elements
// are stored in the coords array. And any arguments after the first array
// are packed into the rest array.
function f([x, y, ...coords], ...rest) {
    return [x+y, ...rest, ...coords]; // Note: spread operator here
}
f([1, 2, 3, 4], 5, 6) // => [3, 5, 6, 3, 4]

🌟NB In ES2018, you can also use a rest parameter when you destructure an object. The value of that rest parameter will be an object that has any properties that did not get destructured. This is a convenient way to get a new object that is a subset of another object.

1
2
3
4
5
// Multiply the vector {x,y} or {x,y,z} by a scalar value, retain other props
function vectorMultiply({x, y, z=0, ...props}, scalar) {
  return { x: x*scalar, y: y*scalar, z: z*scalar, ...props };
}
vectorMultiply({x: 1, y: 2, w: -1}, 2) // => {x: 2, y: 4, z:0, w: -1}

Argument Types

Since JavaScript is a dynamically typed language, you can pass any type of argument to a function. However, it is a good practice to check the type of the arguments passed to a function for better readability and maintainability. This can be done using the typeof operator.

1
2
3
4
5
6
7
8
9
10
11
function arrValSum(arr) {
    let sum = 0;
    if (!Array.isArray(arr)) throw new TypeError("Holder should be an array type");
    for (const val of arr) {
        if (typeof val !== 'number') throw new TypeError("Value must be a number!")
        sum += val;
    }
    return sum;
}

console.log(arrValSum([10,40,32,7,"6"])) // => TypeError: Value must be a number!

Functions as values

Functions are values in JavaScript, just like numbers and strings are values. You can assign a function value to a variable, use it as a property of an object, or pass it as an argument to another function. Functions are also commonly returned from functions.

1
2
3
4
5
// Assign a function value to the constant square
const square = function(x) { return x*x; }

// Compute the square of 3 and return it
square(3) // => 9

When functions are defined in array and object literals, they are called anonymous functions. They don’t have a name identifier after the function keyword.

1
2
const a = [function(x) { return x*x; }, 20]; // Array containing a function and a number
console.log(a[0](a[1])); // => 400: invoke the function at array element 0 on the value at array element 1

You don’t even have to write the function keyword when defining a function value in an array or object literal.

1
2
const a = [x => x*x, 20]; // Array containing a function and a number
console.log(a[0](a[1])); // => 400: invoke the function at array element 0 on the value at array element 1

For example, the following code defines an object that contains a set of arithmetic operations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//functions as values
const ops = {
    add(x, y) {
        return x + y
    }, subtract(x, y) {
        return x - y
    }, multiply(x, y) {
        return x * y
    }, divide(x, y) {
        return x / y
    }, modulo(x, y) {
        return x % y
    }, pow(x, y) {
        return Math.pow(x, y)
    },
}

function operate(operation, first_op, second_op) {
    if (typeof ops[operation] !== "function") throw "no operator found!";
    return ops[operation](first_op, second_op);
}

console.log(operate("modulo", 10, operate("add", 2, 2))); // 10 % 4 => 2

Defining your own function properties

You can define your own properties and methods in a function object. When a function needs a “static” variable whose value persists across invocations, it is often convenient to use a property of the function itself. For example, the following function uses a property named uniqueInteger() to ensure that the values it returns are unique within the lifetime of the program. Since the counter is only used by the function, you don’t need to assign it to a variable but you can access it as a property of the function itself.

1
2
3
4
5
6
7
8
// This function returns a different integer each time it is called.
// It uses a property of itself to remember the next value to be returned.
function uniqueInteger() {
    if (!uniqueInteger.counter) uniqueInteger.counter = 0; // Initialize the property
    return uniqueInteger.counter++;
}
uniqueInteger() // => 0
uniqueInteger() // => 1

Another example that uses a function property is the function factorial() defined earlier in this chapter. It uses a property to cache previously computed values of the factorial function so that it does not have to recompute them on subsequent calls.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Compute factorials and cache results as properties of the function itself.
function factorial(n) {
    if (Number.isInteger(n) && n > 0) { // Positive integers only
        if (!(n in factorial)) { // If no cached result
            factorial[n] = n * factorial(n-1); // Compute and cache it
        }
        return factorial[n]; // Return the cached result
    } else {
        return NaN; // If input was bad
    }
}
factorial[1] = 1; // Initialize the cache to hold this base case.
factorial(6) // => 720

Functions as Namespace

Functions can be used as namespaces to encapsulate code. This is useful when you want to define a set of functions that are related to each other in some way.

1
2
// Define and invoke a function to determine if we're in strict mode.
const inStrictMode = (function() { return this === undefined; }()); // if true, we're in strict mode

Rather than using a function to determine if we’re in strict mode, we can use a function to create a namespace for our code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Define a namespace object and invoke a function to initialize it.
const MyNamespace = (function() {
    // Private data variables
    let myPrivateVar = 0;
    // Private function
    function myPrivateFunc(x) {
        return x+1;
    }
    // Return an object with methods that have access to private data
    // variables and functions.
    return {
        myPublicVar: 1,
        myPublicFunc: function(x) {
            myPrivateVar++;
            return myPrivateFunc(x);
        }
    };
}());

//Don't forget to invoke the function to initialize the namespace object.
console.log(MyNamespace.myPublicFunc(2)) // => 3

Functions as Closures

A closure is a function that has access to the variables from another function’s scope. This is accomplished by creating a function inside a function. The inner function will have access to the variables in the outer function scope, even after the outer function has returned.

In JavaScript, closures are an important concept that allows functions to retain access to variables from their containing scope, even after the outer function has finished executing. Closures are created when inner functions are defined within the body of an outer function and are returned or assigned to a variable.

Here are a few examples to help you understand closures in JavaScript:

Example 1: Basic Closure

1
2
3
4
5
6
7
8
9
10
11
12
function outerFunction() {
  var outerVariable = 'I am from the outer function';

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

var closure = outerFunction();
closure(); // Output: "I am from the outer function"

In this example, innerFunction is defined inside outerFunction and has access to the outerVariable. When outerFunction is called, it returns innerFunction, which is then assigned to the closure variable. Later, when closure is invoked, it still retains access to outerVariable and can log its value.

Example 2: Counter using Closure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function counter() {
  var count = 0;

  function increment() {
    count++;
    console.log(count);
  }

  return increment;
}

var counter1 = counter();
counter1(); // Output: 1
counter1(); // Output: 2

var counter2 = counter();
counter2(); // Output: 1

In this example, the counter function returns an inner function increment, which has access to the count variable defined in the outer function’s scope. Each time increment is called, it increments the count variable and logs its value. Multiple instances of the counter can be created by calling counter(), and each instance maintains its own separate count.

Private variables like counter need not be exclusive to a single closure: it is perfectly possible for two or more nested functions to be defined within the same outer function and share the same scope. Consider the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function incrementValue() {
  let counter = 0;
  return {
    counter() {
      return counter++
    },
    resetCounter() {
      counter = 0
    }
  }
}

let counter1 =incrementValue(), counter2 = incrementValue();
console.log(counter1.counter()); // 0
console.log(counter2.counter()); // 0
//reset 1
counter1.resetCounter();
console.log(counter1.counter()); // 0 -> counter1 is reset
console.log(counter2.counter()); // 1 -> counter2 is not reset

The object above has two methods: counter() returns the next integer, and resetCounter() resets the internal state. The first thing to understand is that the two methods share access to the private variable n. The second thing to understand is that each invocation of incrementValue() creates a new scope— independent of the scopes used by previous invocations—and a new private variable within that scope.

You can combine this closure technique with property getters and setters and parameters too as in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//closures using params, getters and setters
function setUsingParams(param) {
    return {
        get count() {return param++},
        set count(m) {
            if(m > param) param = m
            else throw "Error! Value has to be greater!"
        }
    }
}

 let init = setUsingParams(100);
 console.log(init.count) //100
 console.log(init.count) //101
 console.log(init.count) //102
 init.count = 400; //set count to 400
 console.log(init.count) //400
 console.log(init.count) //401
 init.count = 300; //throws error as 300 is not greater than 400

Example 3: Private Variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function person(name) {
  var privateName = name;

  return {
    getName: function() {
      return privateName;
    },
    setName: function(newName) {
      privateName = newName;
    }
  };
}

var john = person('John');
console.log(john.getName()); // Output: "John"
john.setName('Johnny');
console.log(john.getName()); // Output: "Johnny"

In this example, the person function returns an object with two methods: getName and setName. The privateName variable is only accessible within the returned object’s scope, making it effectively private. The returned methods have closure over the privateName variable, allowing them to access and modify it.

Closures are powerful and widely used in JavaScript to create encapsulated and modular code. They enable the creation of private variables, help manage state, and provide a way to implement function factories or currying, among other use cases.

Closures and Loops

When using closures with loops, avoid using the var keyword to declare variables. Instead, use let or const to declare variables in the loop.

1
2
3
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1); // Output: 0 1 2
}

📓 If you use var to declare the variable i, the output will be 3 for all three iterations of the loop. This is because the closure created by the setTimeout function has access to the same instance of i, which is updated with each iteration of the loop as seen below 👇:

1
2
3
  for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1); // Output: 3 3 3
  }

IIFE (Immediately Invoked Function Expression) as Closures

An IIFE is a JavaScript function that runs as soon as it is defined. It is a design pattern which is also known as a Self-Executing Anonymous Function and contains two major parts. The first is the anonymous function with lexical scope enclosed within the Grouping Operator (). This prevents accessing variables within the IIFE idiom as well as polluting the global scope.

The second part creates the immediately executing function expression () through which the JavaScript engine will directly interpret the function.

1
2
3
(function () {
    statements
})();

The function becomes a function expression which is immediately executed. The variable within the expression can not be accessed from outside it.

1
2
3
4
5
(function () {
    var name = "Barry";
})();
// Variable name is not accessible from the outside scope
name // throws "Uncaught ReferenceError: name is not defined"

The function is executed right after it is defined.

1
2
3
4
(function () {
    var name = "Barry";
    console.log(name); // Output: "Barry"
})();

This pattern is often used when trying to avoid polluting the global namespace, because all the variables used inside the IIFE (like in any other normal function) are not visible outside its scope.

1
2
3
4
5
6
(function () {
    var name = "Barry";
    console.log(name); // Output: "Barry"
})();

console.log(name); // throws "Uncaught ReferenceError: name is not defined"

Basic example of an immediately invoked function expression and a loop with closures:

1
2
3
4
5
6
7
8
9
10
for (var id = 1; id <= 3; id ++) { 
    (function (id) { 
        setTimeout(function () { console.log('seconds: ' + id); }, id* 1000); 
    })(id); // pass id as an argument to the outer function. It will be used as a value for the parameter id of the inner function.
} 
/* Output:
 seconds: 1
 seconds: 2
 seconds: 3
 */

Closures and Object Oriented Programming

Closures are commonly used in object-oriented programming to implement encapsulation and inheritance.

In the example below, the person function returns an object with three methods: getName, setName, and greet. The getName and setName methods have closure over the privateName variable, which is only accessible within the returned object’s scope. The greet method is publicly accessible and has access to the getName method via closure. The greet method also has access to the name parameter passed to the person function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function person(name) {
  var privateName = name;

  return {
    getName: function() {
      return privateName;
    },
    setName: function(newName) {
      privateName = newName;
    },
    greet: function() {
      console.log('Hello, my name is ' + privateName);
    }
  };
}

var john = person('John');
john.greet(); // Output: "Hello, my name is John"
john.setName('Johnny');
john.greet(); // Output: "Hello, my name is Johnny"

Function properties, methods, and constructor

Function properties

Function.length

The length property indicates the number of parameters expected by the function.

1
2
3
4
5
6
7
function func1() {}
function func2(a, b) {}
function func3(a, b, ...theArgs) {}

console.log(func1.length); // 0
console.log(func2.length); // 2
console.log(func3.length); // 2

Function.name

The name property returns the name of the function.

1
2
function doSomething() {}
console.log(doSomething.name); // "doSomething"

Function.prototype

The prototype property represents the prototype for the function.

1
2
function doSomething() {}
console.log(doSomething.prototype); // {constructor: ƒ}

call() and apply()

In JavaScript, call, apply, and bind are methods that can be used with functions to control the context (the value of this) and pass arguments during function invocation. They provide flexibility in how functions are executed. Let’s explore each of them:

  • call: The call method allows you to invoke a function with a specified this value and individual arguments passed as separate parameters.
1
2
3
4
5
6
7
function greet(message) {
  console.log(message + ', ' + this.name);
}

var person = { name: 'John' };

greet.call(person, 'Hello'); // Output: "Hello, John"

In the above example, call is used to invoke the greet function with the person object as the this value and the message 'Hello' as an argument.

Below is a more comprehensive example of call:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//using call()
function Username(name, email) {
  this.name = name;
  this.email = email;
}
function Kenyan(name, email) {
  Username.call(this, name, email);
  this.native = "Kenyan";
}
console.log(new Kenyan("Francis TheJsWizard", "me@mymail.com")) 
/*Output:
Kenyan {
  name: 'Francis TheJsWizard',
  email: 'me@mymail..com',
  native: 'Kenyan'
} 
 */

Another example to quench the thirst 😉👇

1
2
3
4
5
6
7
8
9
10
11
12
13
let animals = [
  {species : 'Lion', rank : 'King'},
  {species : 'Shark', rank : 'Twin'},
];
for(let i=0; i<animals.length; i++) {
  (function(i){
    this.print = function() {
      console.log(`${i}:${this.species}, ${this.rank}`);
    }
    this.print();
  }).call(animals[i], i); // 0:Lion, King 1:Shark, Twin
}
  • apply: The apply method is similar to call, but it takes an array-like object or an actual array as the second parameter to pass arguments to the function.
1
2
3
4
5
6
7
8
function greet(message) {
  console.log(message + ', ' + this.name);
}

var person = { name: 'John' };
var args = ['Hello'];

greet.apply(person, args); // Output: "Hello, John"

In this example, the greet function is invoked using apply, where the args array is passed as the second argument. The elements of the array are used as individual arguments to the function.

The examples illustrated on the call method can also be implemented using apply. For example, the Username and Kenyan functions can be rewritten as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//using apply()
function Username(name, email) {
  this.name = name;
  this.email = email;
}
function Kenyan(name, email) {
  Username.apply(this, [name, email]);
  this.native = "Kenyan";
}
console.log(new Kenyan("Francis TheJsWizard", "me@mymail.com"))
/*Output:
Kenyan {
  name: 'Francis TheJsWizard',
  email: 'me@mymail..com',
  native: 'Kenyan'
 */
  • bind: The bind method returns a new function with the specified this value and partially applied arguments. The returned function can be called later.
1
2
3
4
5
6
7
8
function greet(message) {
  console.log(message + ', ' + this.name);
}

var person = { name: 'John' };
var greetPerson = greet.bind(person, 'Hello');

greetPerson(); // Output: "Hello, John"

In this example, bind is used to create a new function greetPerson that is bound to the person object. The 'Hello' argument is partially applied, and the resulting function can be called independently later.

A more comprehensive example of bind is shown below:

1
2
3
4
5
6
7
8
9
//using bind()
function f(y) { return this.x + y; } // This function needs to be bound
var o = { x: 1 };

var g = f.bind(o); // Calling f.bind sets |this| in f to o.
g(2); // 3

var p = { x: 10, g }; // Calling g as a method of p
console.log(p.g(2)); // 3: g still has its original binding to o, not p 

Arrow functions inherit their this value from the environment in which they are defined, and that value cannot be overridden with bind(), so if the function f() in the preceding code was defined as an arrow function, the binding would not work. The most common use case for calling bind() is to make non-arrow functions behave like arrow functions, however, so this limitation on binding arrow functions is not a problem in practice.

🎡😲 bind() can also perform partial application: any arguments you pass to bind() after the first are bound along with the this value. This partial application feature of bind() does work with arrow functions. Partial application is a common technique in functional programming and is sometimes called currying.

1
2
3
4
5
6
7
8
9
10
11
  function f(a,b,c) { return a+b+c; }
  f(1,2,3); // => 6: normal function call
  
  var g = f.bind(null,1); // Bind the first argument: a = 1
  g(2,3); // => 6: called with 2 and 3
  
  var h = f.bind(null,1,2); // Bind first two arguments: a = 1 and b = 2
  h(3); // => 6: called with 3
  
  var k = f.bind(null,1,2,3); // Bind all three arguments: a = 1, b = 2, and c = 3
  k(); // => 6: no arguments

🔥TakeawaysThe name property of the function returned by bind() is the name property of the function that bind() was called on, prefixed with the word bound. The length property of the bound function is the length of the target function minus the number of arguments that were bound. The prototype property of the bound function is the prototype of the target function. The value of the this keyword inside a bound function is the same as the value of this in the target function when the bound function is invoked. The arguments object inside a bound function contains the arguments passed to the bound function, not the arguments passed to the target function.

1
2
3
4
5
function f() {}
let bound = f.bind();
console.log(bound.name); // "bound f"
console.log(bound.length); // 0: no arguments expected
console.log(bound.prototype); // {}

The primary difference between call and apply is how arguments are passed: call expects individual arguments, while apply takes an array or array-like object. Both call and apply immediately invoke the function. On the other hand, bind returns a new function with a bound context and partially applied arguments, but it does not invoke the function right away.

These methods are particularly useful when working with object-oriented programming, borrowing methods from other objects, or when you need to control the this value explicitly during function invocation.

toString() and valueOf()

Functions have two string representations: a human-readable one that is returned by the toString() method and a machine-readable one that is returned by the valueOf() method.

  • The toString() method is automatically called when a function is represented as a string, as in a template literal or when the function is serialized using JSON.stringify().
  • The valueOf() method is automatically called when a function is represented as a primitive value, as in an arithmetic expression or when the function is serialized using JSON.stringify().
1
2
3
4
5
function f() { return 2; }
f.toString() // => "function f() { return 2; }"
String(f) // => "function f() { return 2; }"
f.valueOf() // => f() { return 2; }
Number(f) // => NaN: functions are not numbers

The Function() Constructor

The Function() constructor is a function that creates and returns a new function. It is similar to eval(), but it takes a string of JavaScript code as its argument instead of a series of statements. The Function() constructor is rarely used because it is less efficient than a function declaration or function expression.

1
2
var add = new Function('a', 'b', 'return a + b');
add(1, 2); // Output: 3

In this example, the Function() constructor is used to create a new function that adds two numbers. The first two arguments are the parameters of the function, and the last argument is the function body.

It creates anonymous functions, which makes debugging difficult. It also creates functions that execute in the global scope, which can cause problems. The Function() constructor is useful when you need to create a function from a string of JavaScript code at runtime.

NB Functions created this way do not use lexical scoping; they always execute in the global scope as if they were top-level functions.

1
2
3
4
5
6
let scope = "global";
function constructFunction() {
  let scope = "local";
  return new Function("return scope"); // Does not capture the local scope!
}
console.log(constructFunction()()); // => "global" not "local"

Functional Programming

Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming paradigm, which means programming is done with expressions or declarations instead of statements.

Functional programming is a style of programming that emphasizes the evaluation of expressions rather than the execution of commands.

Processing Arrays with Functions

To compute the sum of the squares of the elements of an array, you might write code like this:

1
2
3
4
5
let data = [1, 1, 3, 5, 5];
let total = 0;
for(let i = 0; i < data.length; i++) {
  total += data[i] * data[i]; // total = 61
}

This code is not very readable, and it is not very reusable. It is also not very efficient because it uses a loop to iterate over the array.

A more functional approach to this problem is to use the map() and reduce() methods of arrays:

1
2
3
4
5
let data = [1, 1, 3, 5, 5];
const map = function(a, ...args) { return a.map(...args); }; // function to invoke map() method
const reduce = function(a, ...args) { return a.reduce(...args); }; // function to invoke reduce() method
let sumOfSquares = reduce(map(data, x => x*x), (a, b) => a + b);
console.log(sumOfSquares); // => 61

The map() method takes an array, applies a function to each element of the array, and returns an array of the results.

The reduce() method takes an array and combines it into a single value by repeatedly applying a function that combines an element of the array with a base value.

The map() and reduce() methods are higher-order functions because they take functions as arguments.

higher-order functions

Higher-order functions are functions that take other functions as arguments or return functions as their results.

1
2
3
4
5
6
7
8
9
10
11
12
13
// This higher-order function returns a new function that
passes its
// arguments to f and returns the logical negation of f's
return value;
function not(f) {
return function(...args) { // Return a new function
let result = f.apply(this, args); // that calls f
return !result; // and negates its result.
};
}
const even = x => x % 2 === 0; // A function to determine if a number is even
const odd = not(even); // A new function that does the opposite
[1,1,3,5,5].every(odd) // => true: every element of the array is odd

not() is a higher-order function that takes a function as its argument and returns a new function that returns the logical negation of the original function.

Partial Application of Functions

Partial application is the process of fixing a number of arguments to a function, producing another function of smaller arity.

1
2
3
4
5
function sumThreeNumbers(a, b, c) {
  return a + b + c;
}
const sum = sumThreeNumbers.bind(null, 1, 2);
sum(3); // => 6

In this example, the bind() method is used to create a new function that is like the sumThreeNumbers() function but with the first two arguments fixed to the values 1 and 2.

Memoization

Memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function memoize(fn) {
  const cache = {};
  return function(...args) {
    if (cache[args]) {
      return cache[args];
    }
    const result = fn.apply(this, args);
    cache[args] = result;
    return result;
  };
}

function slowFib(n) {
  if (n < 2) {
    return n;
  }
  return fib(n - 1) + fib(n - 2);
}

const fib = memoize(slowFib);

fib(15); // => 610

In this example, the memoize() function is used to create a new function that is like the slowFib() function but that caches the results of previous calls to fib().

This brings us to the end of this article where we have learned about functions in JavaScript. We have also learned about the different ways of creating functions in JavaScript.

Made with ❤ by LucasGithub.Twitter
This post is licensed under CC BY 4.0 by the author.