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.
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
This is lame...
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)
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