Javascript function part 2/4: closure

This tutorial is a part of the Learn everything about Javascript in one course.

What is closure?

A closure (a.k.a lexical closure or function closure), is a technique for implementing lexically scoped name binding in a language with first-class functions. Closure is a property of function and other programming languages' function also have it.

In simple language, closure:

  • Is created whenever a function (inner function) created inside another function (outer function).
  • All variable defined at outer function will be remembered and can be used by inner function.
closure.js
// What is closure?

function createCounter() { // outer function
  let count = 0 // closure data, is "remember" and "can be used" by inner function
  return { // an object with 3 methods is returned
    increase: () => count = count + 1, // inner function
    decrease: () => count = count - 1, // inner function
    print: () => console.log(count), // inner function
  }
}

const counter = createCounter()

counter.print() // 0
counter.increase()
counter.print() // 1
counter.decrease()
counter.decrease()
counter.print() // -1

What is closure used for?

Closure is good to use whenever you have a function when running, it depends on:

  • Shared data between runs.
  • Shared data is encapsulated in one single function.

In general, closure gives you shared data between different runs of a function . The case of usage is pretty much depends on how you creatively use it.

setTimeout()to run a function after specified time

Syntax setTimeout(functionToRun, timeMiliseconds). setTimeout() is a a built-in, global function that you can call anywhere in your program.

setTimeout.js
// setTimeout()to run a function after specified time

const printAfter500ms = () => {
  console.log('this message is printed after 500 ms')
}

setTimeout(printAfter500ms, 500)
setTimeout(() => console.log('this message is printed after 100 ms'), 100)

// this message is printed after 100 ms
// this message is printed after 500 ms

Javascript closure example 1: delay calling a function

Problem:

  • Assume you were a Goolge software engineer and tasked to write a function that return search suggestion after user type in.
  • Search suggestion is the list of suggestions on Google Search Bar after each of your keystroke. Your task is to write a function called getSuggestion().
  • Assume that we are at the early days of the internet and Google servers are limited. So your getSuggestion() only makes a request to the server 1 second after the last keystroke.

Analysis:

  • getSuggestion() needs to remember to go fetch suggestion 1 second after 1st keystroke.
  • getSuggestion() needs to remember the last keyword.
  • getSuggestion() needs to remember if there is any pending fetch.
  • Data is shared between different runs -> using closure is a good way to solve this problem.
searchSuggestion.js
// Javascript closure example: delay calling a function

const TOP_BOTTOM = 'TOP_BOTTOM'
const LABEL = 'FIRST_CALL'

console.time(TOP_BOTTOM) // timer TOP_BOTTOM start
console.time(LABEL) // timer FIRST_CALL start 

const createGetSuggestion = () => { // outer function
  let ok = false // closure data, true for go fetch suggestion data
  let pending = false // closure data
  let keyword = '' // closure data

  const get = (word) => { // inner function
    keyword = word
    if(ok) {
      console.log(`now get suggestions with keyword '${keyword}'`) // make call to server and get suggestion
      console.timeEnd(LABEL); // timer FIRST_CALL end
      pending = false
      ok = false
    } else {
      console.log('please wait')
      if(!pending) {
        pending = true
        setTimeout(() => {
          ok = true
          get(keyword)
        }, 1000); // timeout in 1 second
      }
    }
  }

  return get // inner function returned
}

const getSuggestion = createGetSuggestion()

// each keystroke will call "getSuggestion()" once
getSuggestion('i') // please wait (1st keystroke)
getSuggestion('ir') // please wait (2nd keystroke)
getSuggestion('iro') // please wait (3rd keystroke)
getSuggestion('iron') // please wait (4th keystroke)
getSuggestion('iron ') // please wait (5th keystroke)
getSuggestion('iron m') // please wait (6th keystroke)
console.timeEnd(TOP_BOTTOM); // TOP_BOTTOM: 6.192ms
// now get suggestions with keyword 'iron m'
// FIRST_CALL: 1008.117ms

Javascript closure example 2: memoization

Memoization or memoisation 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.

Problem:

  • Assume that getting UTF16 code of a character is a very expensive computing problem.
  • Write a function call getCodeAndCache() that do "heavy" UTF16 look up for never "seen" input.
  • getCodeAndCache() should cache result of "seen" input and return cached result if see the same input again.

Analysis:

  • getCodeAndCache() needs to remember input & output.
  • Data is shared between different runs -> using closure is a good way to solve this problem.
memoization.js
// Javascript closure example: memoization

const createGetCodeAndCache = () => { // outer function
  const cache = {} // closure data

  const get = (input) => { // inner function
    console.log(cache) // print cache before each run
    if(cache[input]) { // use cache if available
      console.log('cached output')
      return cache[input]
    } else { // compute if no cache available
      console.log('new input')
      const result = input.charCodeAt(0)
      cache[input] = result // put data to cache
      return result
    }
  }

  return get
}

const getCodeAndCache = createGetCodeAndCache()

console.log(getCodeAndCache('a'))
// {}, empty cache
// new input
// 97

console.log(getCodeAndCache('b'))
// { a: 97 }, cached 'a'
// new input
// 98

console.log(getCodeAndCache('a')) // "seen" input
// { a: 97, b: 98 }, cached 'a' and 'b'
// cached output
// 97

Summary

  • Closure function has shared data between different runs.
  • Use of deeply nested closure should be avoided as it consumes a lot of resources.