Function parameter definitions in ES6
I'm sure that this is relatively straightforward and that I'm missing something obvious. I'm reading through Mozilla's tutorials on ES6, and their chapter on destructuring contains the following pattern:
FUNCTION PARAMETER DEFINITIONS
As developers, we can often expose more ergonomic APIs by accepting a single object with multiple properties as a parameter instead of forcing our API consumers to remember the order of many individual parameters. We can use destructuring to avoid repeating this single parameter object whenever we want to reference one of its properties:
function removeBreakpoint({ url, line, column }) { // ... }
This is a simplified snippet of real world code from the Firefox DevTools JavaScript debugger (which is also implemented in JavaScript—yo dawg). We have found this pattern particularly pleasing.
What I don't understand is how this relates to destructuring. Is the idea that you permit the ability to pass an object into this function that can be in arbitrary order as long as it contains all items, i.e. { line: 10, column: 20, url: 'localhost' }
?
If so, what is the benefit over something like
function removeBreakpoint(params) {
// ...
}
where params is an object with url
, line
, and column
? Is the idea just that you force Javascript to validate a function's parameters when used in a destructured context by explicitly defining them?
Solution 1:
What I don't understand is how this relates to destructuring.
Within removeBreakpoint
, you can use url
, line
, and column
directly. The destructuring happens when removeBreakpoint
is called with an options object; that object's matching properties are destructured into individual arguments.
Is the idea that you permit the ability to pass an object into this function that can be in arbitrary order as long as it contains all items, i.e. { line: 10, column: 20, url: 'localhost' }?
Yes, but it doesn't have to contain all the items; if it doesn't, then since the argument is initialized from an object property that doesn't exist, the argument is undefined
(unless a default value is specified).
Simple example demonstrating the destructuring (Live Copy with ES5 translation on Babel's REPL):
"use strict";
function removeBreakpoint({ url, line, column }) {
console.log("removeBreakpoint:");
console.log("url: " + url);
console.log("line: " + line);
console.log("column: " + column);
}
removeBreakpoint({
url: "the url",
line: "the line",
column: "the column"
});
removeBreakpoint({
url: "the url",
line: "the line"
});
Output:
removeBreakpoint: url: the url line: the line column: the column removeBreakpoint: url: the url line: the line column: undefined
If so, what is the benefit over something like
function removeBreakpoint(params) { // ... }
where params is an object with url, line, and column?
Syntactic sugar. The new syntax for accepting options objects is more concise and declarative, automating a common pattern. This is particularly apparent when you combine it with default values (Live Copy):
"use strict";
function removeBreakpoint(
{ // <= { starts destructuring arg
url = "url default", // <= Default for `url`
line = "line default", // <= ...for `line`
column = "column default" // <= ...for `column`
} // <= } ends destructuring arg
= {} // <= Default for the options object iself
) { // (see notes after the code block)
console.log("removeBreakpoint:");
console.log(url);
console.log(line);
console.log(column);
}
removeBreakpoint({
url: "the url",
line: "the line",
column: "the column"
});
removeBreakpoint({
url: "the url",
line: "the line"
});
removeBreakpoint();
Output:
removeBreakpoint: the url the line the column removeBreakpoint: the url the line column default removeBreakpoint: url default line default column default
In the above, even the options object itself is optional, which is why the last call works:
removeBreakpoint();
If we hadn't given a default for the options object itself, that call would have failed because we'd be trying to read the property url
of undefined
. Sometimes you want that, and so you'd leave the overall option off. Other times you don't.
Side note: The ability to default parts of the options object and also, separately, the entire options object leads to an interesting situation where you can have different defaults depending on whether an options object was given but didn't have a specific option vs. no options object being given at all, all done declaratively: Live Copy
"use strict";
function test(
num,
{
foo = "foo default",
bar = "options given without bar"
} = {bar: "options not given at all"}
) {
console.log(num + ": foo = " + foo + ", bar = " + bar);
}
test(1, {foo: "foo value", bar: "options given with bar"});
test(2, {bar: "options given with bar"});
test(3, {});
test(4);
Output:
1: foo = foo value, bar = options given with bar 2: foo = foo default, bar = options given with bar 3: foo = foo default, bar = options given without bar 4: foo = foo default, bar = options not given at all