- Note 6. ES6: Default values of parameters
- Dmitry Soshnikov
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: Chorer
- Proofreader: FireairForce, XingqiwU55555
In this article we’ll introduce another ES6 feature, function arguments with default values. As we will see, there are delicate cases.
The previous default parameter values were handled manually in the following optional ways:
function log(message, level) {
level = level || 'warning';
console.log(level, ':', message);
}
log('low memory'); // warning: low memory
log('out of memory'.'error'); // error: out of memory
Copy the code
To avoid cases where arguments are not passed, it is common to see typeof checks:
if (typeof level == 'undefined') {
level = 'warning';
}
Copy the code
Sometimes, you can also check arguments.length:
if (arguments.length == 1) {
level = 'warning';
}
Copy the code
All of these methods work, but they are too manual and not abstract enough. ES6 standardizes a syntactic structure that defines parameter defaults directly in function headers.
ES6 Default value: Basic instance
Default parameter values exist in many languages, so most developers should be familiar with their basic form:
function log(message, level = 'warning') {
console.log(level, ':', message);
}
log('low memory'); // warning: low memory
log('out of memory'.'error'); // error: out of memory
Copy the code
This default parameter is fairly arbitrary, but convenient. Next, let’s dive into the implementation details to clear up the confusion that default parameters can cause.
Implementation details
Here are some implementation details about default parameter values for ES6 functions.
Recalculation at the execution stage
In contrast to some other languages (such as Python), which evaluate default parameters once at the definition stage, ECMAScript evaluates default parameters at the execution stage — every time a function is called. This design is used to avoid confusion with complex objects that are the default values. Consider the following Python example:
def foo(x = []):
x.append(1)
return x
We can see the default value at function definition
# created only once and saved in
Properties of the function object
print(foo.__defaults__) # ([])
foo() # [1]
foo() # (1, 1)
foo() # (1, 1, 1)
# As we said, the reason is:
print(foo.__defaults__) # ([1, 1, 1],)
Copy the code
To avoid this, Python developers make a habit of defining the default value as None, and explicitly checking this value:
def foo(x = None):
if x is None:
x = []
x.append(1)
print(x)
print(foo.__defaults__) # (None,)
foo() # [1]
foo() # [1]
foo() # [1]
print(foo.__defaults__) # ([None],)
Copy the code
However, this is just as inconvenient as handling the actual defaults manually, and the initial case is confusing. So, to avoid this, ECMAScript evaluates the default value every time a function executes:
functionfoo(x = []) { x.push(1); console.log(x); } foo(); // [1] foo(); // [1] foo(); / / [1]Copy the code
Everything is nice and intuitive. As you’ll see, ES semantics can be confusing if we don’t understand how defaults work.
Masking of the external scope
Consider the following example:
var x = 1;
functionfoo(x, y = x) { console.log(y); } foo(2); // 2, not 1!Copy the code
As we can see, the above example outputs y as 2, not 1. The reason is that the x in the argument is not the same as the global x. Since the execution phase evaluates the default value, by the time the assignment of = x occurs, x has already been resolved in the internal scope and refers to the x parameter itself. The parameter x with the same name obscures the global variable so that all access to x from the default value points to the parameter.
Parameter TDZ (temporary dead zone)
ES6 refers to what is called TDZ (temporary dead zone) – the part of the program where a variable or parameter cannot be accessed until it is initialized (that is, receives a value).
In terms of parameters, a parameter cannot have its own default value:
var x = 1;
functionFoo (x = x) {// Throws an error! . }Copy the code
The assignment = x we mentioned above resolves x in the parameter scope, obscuring the global x. However, parameter X is within TDZ and cannot be accessed until initialization. Therefore, it cannot be initialized to itself.
Note that the above example with y is valid because x is already initialized (undefined by implicit default). Let’s take a look again:
functionFoo (x, y = x) {// Possible... }Copy the code
This works because the parameters in ECMAScript are initialized from left to right, and we already have x available.
We mentioned that parameters are already associated with an “inner scope”, which in ES5 we can assume is the scope of the function body. However, it is actually more complex: it may be a function scope, or an intermediate scope created specifically to store parameter bindings. So let’s think about it.
Intermediate scope for a specific parameter
In fact, if some (at least one) parameters have default values, ES6 defines an intermediate scope to store the parameters, and this scope is not shared with the scope of the function body. This is one of the major differences with ES5. We use examples to prove:
var x = 1;
function foo(x, y = function() { x = 2; }) { var x = 3; y(); // is' x 'shared? console.log(x); // no, still 3, not 2} foo(); // And external 'x' is not affected console.log(x); / / 1Copy the code
In this example, we have three scopes: global, parameter, and function:
{x: 3} // internal -> {x: undefined, y: undefinedfunction() { x = 2; }} // Parameters -> {x: 1} // globalCopy the code
We can see that when function Y executes, it parses X in the nearest environment (that is, the parameter environment) to which the function scope is not visible.
Translated into ES5
If we were to compile ES6 code to ES5 and see what this intermediate scope looks like, we would get the following result:
// ES6
function foo(x, y = function() { x = 2; }) { var x = 3; y(); // is' x 'shared? console.log(x); } // Compile to ES5functionFoo (x, y) {// Set default values.if (typeof y == 'undefined') {
y = function() { x = 2; }; // Now you can clearly see that it updates the parameter 'x'}return function() { var x = 3; // Now it is clear that 'x' comes from the inner scope y(); console.log(x); }.apply(this, arguments); }Copy the code
The source of the parameter scope
But what exactly is the purpose of setting the scope for this parameter? Why can’t we share parameters with function bodies like ES5 does? The reason: variables with the same name in the function body should not affect the capture behavior in the closure binding because they have the same name.
Let’s use the following example:
var x = 1;
function foo(y = function() { returnx; }) {// capture 'x' var x = 2;returny(); } foo(); // It is 1, not 2Copy the code
If we create function y in the scope of the function body, it will capture the internal x, which is 2. But obviously, it should capture the external x, that is, 1 (unless it is shaded by the argument of the same name).
Also, we cannot create functions in an external scope, which means we cannot access parameters from such functions. We can do this:
var x = 1;
function foo(y, z = function() { returnx + y; }) {var x = 3;returnz(); } foo(1); // 2, not 4Copy the code
When parameter scopes are not created
The semantics above are quite different from the manual implementation of the default values:
var x = 1;
function foo(x, y) {
if (typeof y == 'undefined') {
y = function() { x = 2; }; } var x = 3; y(); // is' x 'shared? console.log(x); / / yes! 2 } foo(); // External 'x' is still unaffected console.log(x); / / 1Copy the code
Now here’s an interesting fact: if a function doesn’t have a default value, it doesn’t create this intermediate scope and shares it with a parameter binding in the function environment, which runs in ES5 mode.
Why is it so complicated? Why not always create parameter scopes? Is it just about optimization? Not so. Specifically, this is for downward compatibility with ES5: the code above that implements the defaults manually should update the x in the function body (that is, the parameters themselves, and in the same scope).
Also note that those repeated declarations only apply to var and functions. Repeating arguments with let or const does not work:
function foo(x = 5) {
letx = 1; // error const x = 2; / / error}Copy the code
undefined
check
Also note the interesting fact that whether the default value is applied depends on whether a check on the initial value of the parameter (whose assignment occurs as soon as it is entered into context) results in undefined. Let’s prove it:
functionfoo(x, y = 2) { console.log(x, y); } foo(); // undefined, 2 foo(1); // 1, 2 foo(undefined, undefined); // undefined, 2 foo(1, undefined); / / 1. 2Copy the code
Normally, parameters with default values come after required parameters in programming languages, but the above fact allows us to use the following structure in JavaScript:
functionfoo(x = 2, y) { console.log(x, y); } foo(1); // 1, undefined foo(undefined, 1); / / 2, 1Copy the code
Deconstruct the default value of the component
Another place where defaults are involved is in the default values of the deconstructed component. This article will not cover the topic of deconstructing assignment, but we will show some small examples. Whether you use deconstruction in function arguments or use simple defaults as described above, you deal with defaults the same way: create two scopes if necessary.
functionfoo({x, y = 5}) { console.log(x, y); } foo({}); // undefined, 5 foo({x: 1}); // 1, 5 foo({x: 1, y: 2}); / / 1. 2Copy the code
Although the default value for deconstruction is more general, not just for functions:
var {x, y = 5} = {x: 1}; console.log(x, y); / / 1, 5Copy the code
conclusion
Hopefully this short note will help explain the details of default values in ES6. Note that as of the writing of this article (August 21, 2014), the default values have not actually been implemented (they all just create a scope shared with the function body), because this “second scope” was recently added to the draft standard. Default values are sure to be a useful feature that will make our code more elegant and tidy.
If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.