Closures

Definition

Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.

Example

function createMultipler (x) {
  return function multiply (y) {
    return x * y
  }
}

const multiplyBy10 = createMultipler(10)
console.log(multiplyBy10(5)) // 50

// es6
const createMultipler = x => y => x * y

But wait!

The same can be done by simply passing 2 variables.

This is lame...

Not really!

Consider those cases:

  • Your function requires lots of repeatable configuration
  • More generic functions
  • The context where the function is called, doesn’t have access to the environment

So instead

You can do

function saveData (x, y, z, a, b) {
  state[x][y][z][a] = b
}

function removeRecord (x, y, z, a, i) {
  state[x][y][z][a].splice(i, 1)
}

saveData(
  X, Y, Z, 
  A, B
)

removeRecord(
  X, Y, Z, 
  A, index
)
function mutationsFor (x, y, z) {
  return {
    saveData (a, b) {
      state[x][y][z][a] = b
    },
    removeRecord (a, i) {
      state[x][y][z][a].splice(i, 1)
    }
  }
}

const mutationsXYZ = mutationsFor(X, Y, Z)

mutationsXYZ.saveData(A, B)

mutationsXYZ.removeRecord(A, index)

Your function requires lots of repeatable configuration

So instead

You can do

let viewModel = {
  email: '',
  password: ''
}

function isSameAsPassword (string) {
  return string === viewModel.password
}

function isSameAsEmail (string) {
  return string === viewModel.email
}

function longerThan6 (string) {
  return string.length > 6
}

function longerThan10 (string) {
  return string.length > 10
}
let viewModel = {
  email: '',
  password: ''
}

function isSameAs (prop) {
  return function (v) {
    return v === viewModel[prop]
  }
}

function longerThan (x) {
  return function (string) {
    return string.length > x
  }
}


// es6
const isSameAs = prop => v => v === viewModel[prop]

Generic functions

const model = {
  locations: {
    value: [],
    searchResults: [],
    searchHandler: findIn('locations', 'locations'),
    validators: [minLength(5), noDupes('locationsExcluded')] 
  },
  locationsExcluded: {
    value: [],
    searchResults: [],
    searchHandler: findIn('locations', 'locationsExcluded'),
    validators: [minLength(5), noDupes('locations')] 
  },
  products: {
    value: [],
    searchResults: [],
    searchHandler: findIn('products', 'products'),
    validators: [maxLength(3), noDupes('productsExcluded')]
  },
  productsExluded: {
    value: [],
    searchResults: [],
    searchHandler: findIn('products', 'productsExcluded'),
    validators: [noDupes('products')]
  }
}

const noDupes = path, context => value => {
  return model[context].value.includes(value)
}

const findIn = (path, context) => async (query) => {
  model[context].searchResults = await api.get(`api/v1/${path}`, query)
}

If context where the function is called, doesn’t have access to the environment

function flattenOptions (values, label) {
  return (options) =>
    options.reduce((prev, curr) => {
      if (curr[values] && curr[values].length) {
        prev.push({
          $groupLabel: curr[label],
          $isLabel: true
        })
        return prev.concat(curr[values])
      }
      return prev.concat(curr)
    }, [])
}
function filterGroups (search, label, values, groupLabel) {
  return (groups) =>
    groups.map(group => {
      const groupOptions = filterOptions(group[values], search, label)

      return groupOptions.length
        ? {
            [groupLabel]: group[groupLabel],
            [values]: groupOptions
          }
        : []
    })
}
function stripGroups (options) {
  return options.filter(option => !option.$isLabel)
}
function filterOptions (options, search, label) {
  return label
    ? options.filter(option => includes(option[label], search))
    : options.filter(option => includes(option, search))
}

Real life examples

// also called compose, pipe
const flow = (...fns) => x => fns.reduce((v, f) => f(v), x)


// from Vue component where `this` is the viewModel
methods: {
  filterAndFlat (options) {
    return flow(
      filterGroups(this.search, this.label, this.groupKey, this.groupLabel),
      flattenOptions(this.groupKey, this.groupLabel)
    )(options)
  },
  flatAndStrip (options) {
    return flow(
      flattenOptions(this.groupKey, this.groupLabel),
      stripGroups
    )(options)
  }
}

Function composition

// reactive getters in Vue that can have dependencies
computed: {
  filteredOptions () {
    let search = this.search || ''
    return this.options = this.groupKey
      ? this.filterAndFlat(options, search, this.label)
      : filterOptions(options, search, this.label)
  },
  optionKeys () {
    const options = this.groupKey 
      ? this.flatAndStrip(this.options) 
      : this.options

    return this.label
      ? options.map(element => element[this.label].toString().toLowerCase())
      : options.map(element => element.toString().toLowerCase())
  }
}

Usage

Closures

By Damian Dulisz

Closures

  • 1,656