CoffeeScript supports a subset of pattern matching called destructuring assignment. Destructuring assignment uses patterns to extract out the parts of an object during assignment. It’s a simple and elegant technique but when overused can have unintended consequences on a codebase.
CoffeeScript’s two data structures, Arrays and Objects, can both be destructured. Let’s first take a look at destructuring Arrays.
The “Hello World” of destructuring assigment is using Arrays to swap two variables.
coffee> x = 1 1 coffee> y = 2 2 coffee> [x, y] = [y, x] [ 2, 1 ] coffee> x 2 coffee> y 1
In our assignment statement, we matched our lvalue pattern to the structure of our rvalue, swapping x and y.
Array destructuring can unpack the result of functions that return multiple values.
coffee> [firstName, lastName] = 'Foo Bar'.split ' ' [ 'Foo', 'Bar' ] coffee> firstName 'Foo' coffee> lastName 'Bar' coffee> [phoneNumber, prefix, lineNumber] = '555-1212'.match /(\d{3})-(\d{4})/ [ '555-1212', '555', '1212', index: 0, input: '555-1212' ] coffee> phoneNumber '555-1212' coffee> prefix '555' coffee> lineNumber '1212'
CoffeeScript’s splat (...
) can destructure a subset of an Array.
coffee> [head, tail...] = [1..10] [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] coffee> head 1 coffee> tail [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
Object destructuring is used to extract an Object’s state. Think of it as the opposite of construction.
coffee> user = name: 'Foo', age: 42 { name: 'Foo', age: 42 } coffee> { name: name, age: age } = user { name: 'Foo', age: 42 } coffee> name 'Foo' coffee> age 42
Again, our lvalue pattern matched the structure of our rvalue, in this case, an Object. However, the destructuring syntax to define a variable with the same name as an Object’s property is repetitive. A shorter syntax is available.
coffee> { name, age } = user { name: 'Foo', age: 42 } coffee> name 'Foo' coffee> age 42
Destructuring patterns can match an Object’s structure to any depth.
coffee> user = name: 'Foo', age: 42, address: { city: 'Anytown', state: 'AL' } { name: 'Foo', age: 42, address: { city: 'Anytown', state: 'AL' } } coffee> { address: { city, state } } = user { name: 'Foo', age: 42, address: { city: 'Anytown', state: 'AL' } } coffee> city 'Anytown' coffee> state 'AL'
Here we extracted parts of the user’s nested address Object using the shorter object pattern syntax.
Function parameter lists can destructure Object parameters to eliminate local variables.
displayName = ({ name, age }) -> console.log "#{name}, #{age} year(s) old" coffee> displayName name: 'Foo', age: 42 Foo, 42 year(s) old
Default function parameter values can be combined with destructuring.
displayName = ({ name, age } = { name: 'Unknown', age: 0 }) -> console.log "#{name}, #{age} year(s) old" coffee> displayName name: 'Foo', age: 42 Foo, 42 year(s) old coffee> displayName() Unknown, 0 year(s) old
Destructuring the items in a collection while iterating over it is another common use case for destructuring.
coffee> users = [] [] coffee> users.push name: 'Foo', age: 42 1 coffee> users.push name: 'Bar', age: 15 2 coffee> users.push name: 'Baz', age: 25 3 coffee> age for { age } in users when age > 21 [ 42, 25 ]
Object destructuring allows you to easily extract an Object’s state. However, the code is now dependent on implementation details, increasing coupling, and making refactoring difficult. OO-purists see this as a violation of the Law of Demeter.
Anytime you find yourself destructuring an Object, ask why you’re ripping it apart. Then, look for ways to make the Object responsible for the behavior. Destructuring is very convenient, but make sure you’re aware of its effects on the cohesion of a codebase.