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,646