Scope of Default function parameters in javascript
Because the default initialisers are not evaluated in the parent scope, but rather inside the function scope. The parameters themselves are already in scope, so that you can do something likeI don't understand why these examples are not good
(function(a = 2, b = a) { console.log(b); }());
The relevant section is §9.2.12 FunctionDeclarationInstantiation.Could somebody please point me to place in specification which could clarify this?
Yes it is, although it's written for engine implementors not for programmers. However, the explanatory note basically confirms your understanding of the optimisationI must say that specification is rather hard to understand.
If the function’s formal parameters do not include any default value initializers then the body declarations are instantiated in the same Environment Record as the parameters. If default value parameter initializers exist, a second Environment Record is created for the body declarations.
Your examples basically desugar to
(function() {
let a = arguments[0] !== undefined ? arguments[0] : b,
// ^ clearly a ReferenceError
b = arguments[1] !== undefined ? arguments[1] : 1;
{
}
})();
(function() {
let c = 1;
return (function() {
let a = arguments[0] !== undefined ? arguments[0] : c;
// ^ works as you'd think
{
return a === 1;
}
})();
})();
(function() {
let a = 1;
(function() {
let a = arguments[0] !== undefined ? arguments[0] : a;
// ^ again clearly a ReferenceError
{
}
})();
})();
(function() {
let b = 1;
(function() {
let a = arguments[0] !== undefined ? arguments[0] : b,
// ^ still a ReferenceError
b = arguments[1] !== undefined ? arguments[1] : 2;
{
}
})();
})();
(function() {
let a = arguments[0] !== undefined ? arguments[0] : () => b,
// ^ works indeed
b = arguments[1] !== undefined ? arguments[1] : 2;
{
return a() === 2;
}
})();
JS: Default function parameter values and scope
It's because default initialisers run in their own scope. Only if there are none, the body code is evaluated in the top function scope. It would only make a difference if you put a function expression in a default initaliser, which may close over the other parameters but does not have access to the variables that will be declared in the body.
Basically it's the difference between
function steps() {
var n = arguments[0],
i = arguments[1];
var output = [n];
}
andfunction steps() {
var n = arguments[0],
i = arguments.length > 0 ? arguments[1] : 40;
(() => {
var output = [n];
}());
}
What does declaring default parameters in a function do under the hood?
That's a pretty good explanation! Yes, the parameter list has its own scope. Let me expand on your explanation a bit and then explain why the additional scope there.
When you call a function without default parameter values, a new scope is created for the body of the function, and the parameters are created in that scope like top-level variables in the function. So, conceptually:
<<
let param1 = /*...the argument value provided for param 1 if any */;
let param2 = /*...the argument value provided for param 2 if any */;
// Start of function code
var variable1;
let variable2;
// End of function code
>>
(I'm using <<
/>>
delimiters instead of {
/}
because the scopes aren't just block scopes, they isolate var
as well; so I picked an arbitrary delimiter.)When there are default parameter values, as you've described, there's an additional scope involved:
<<
let param1 = /*...the argument value provided for param 1 if any */;
let param2 = /*...the argument value provided for param 2 if any */;
<<
// Start of function code
var variable1;
let variable2;
// End of function code
>>
>>
The reason is that default parameter values are expressions, not just literals. For example:function example(a = 1, b = a + 1) {
// ^^^^^−−−−−−−−−−−−− expression, not just literal
return a + b;
}
console.log(example()); // 1 + (1 + 1) = 3
console.log(example(2)); // 2 + (2 + 1) = 5
Function parameter scope in javascript
Referencing name
outside the function doesn't throw an error like you would expect because it is actually a global variable in every page, part of the global window
object. Typing name
is the same as window.name
.
The something
variable causes an error because it hasn't been defined yet. However, the name variable doesn't cause any problems because it is blank by default, at least in Chrome. You are correct that variables created in a function don't exist outside it.
See https://developer.mozilla.org/en-US/docs/Web/API/Window/name for details.
Default value of argument in ES6
That function has destructuring and default parameters mixed together.
Based on the function signature, we can say that we're expecting a single argument, which should be an object.
function list(myObject) {
}
If no arguments are passed (or undefined
is passed), we setup a default value to be an empty object, {}
.function list(myObject = {}) {
}
Now, no matter if we pass an object, no arguments, or undefined
, myObject
will be an object.// myObject will be {} for all 3 calls
list({})
list()
list(undefined);
Next, we destructure this myObject
by extracting skip
and limit
from it:function list(myObject = {}) {
let { skip, limit } = myObject;
}
Furthermore, we can perform this destructuring directly instead of the myObject
parameter:function list({ skip, limit } = {}) {
}
Finally, in case skip
or limit
do not exist on the value we end up with, we give them default values:function list({ skip = 0, limit = 50 } = {}) {
}
JavaScript function parameter and scope
This is because when you declare the parameter x
function bar(x) { /*...*/ }
You are "hiding" the global variable x. You only have access to the parameter xtry passing in the global x
function bar(x) { alert(x); }
var x = 'I am defined outside bar definition';
bar(x); // here the x is the global x that we pass in
// the function gets a reference to the global x
If we omit the parameter then the global x would become visible againfunction baz() { alert(x); }
var x = 'global var';
baz(); // 'global var'
Note that global variables should be limited in good js applications. Why can't a default parameter and function parameter have the same name?
OK, I haven't read the spec itself because the last Krell brain boost I had is wearing off, but I think the issue is that the right-hand side expressions for default parameters include the set of parameters in their scope. Thus, the a
you reference on the right-hand side of the =
is the a
on the left-hand side, not the a
in the enclosing context. (The context is available of course, as you note, but parameter names shadow that context.)
The evaluation of the right-hand side default expressions is an invocation time thing in ES2015.
What is the scope of variables in JavaScript?
TLDR
JavaScript has lexical (also called static) scoping and closures. This means you can tell the scope of an identifier by looking at the source code.
The four scopes are:
- Global - visible by everything
- Function - visible within a function (and its sub-functions and blocks)
- Block - visible within a block (and its sub-blocks)
- Module - visible within a module
var
(function scope), let
(block scope), and const
(block scope). Most other forms of identifier declaration have block scope in strict mode.Overview
Scope is the region of the codebase over which an identifier is valid.
A lexical environment is a mapping between identifier names and the values associated with them.
Scope is formed of a linked nesting of lexical environments, with each level in the nesting corresponding to a lexical environment of an ancestor execution context.
These linked lexical environments form a scope "chain". Identifier resolution is the process of searching along this chain for a matching identifier.
Identifier resolution only occurs in one direction: outwards. In this way, outer lexical environments cannot "see" into inner lexical environments.
There are three pertinent factors in deciding the scope of an identifier in JavaScript:
- How an identifier was declared
- Where an identifier was declared
- Whether you are in strict mode or non-strict mode
var
,let
andconst
- Function parameters
- Catch block parameter
- Function declarations
- Named function expressions
- Implicitly defined properties on the global object (i.e., missing out
var
in non-strict mode) import
statementseval
- Global context
- Function body
- Ordinary block
- The top of a control structure (e.g., loop, if, while, etc.)
- Control structure body
- Modules
Declaration Styles
var
Identifiers declared usingvar
have function scope, apart from when they are declared directly in the global context, in which case they are added as properties on the global object and have global scope. There are separate rules for their use in eval
functions.let and const
Identifiers declared usinglet
and const
have block scope, apart from when they are declared directly in the global context, in which case they have global scope.Note: let
, const
and var
are all hoisted. This means that their logical position of definition is the top of their enclosing scope (block or function). However, variables declared using let
and const
cannot be read or assigned to until control has passed the point of declaration in the source code. The interim period is known as the temporal dead zone.
function f() {
function g() {
console.log(x)
}
let x = 1
g()
}
f() // 1 because x is hoisted even though declared with `let`!
Using a function call as default parameter javascript
Your variable names are conflicting. You have a rgb
function on the top level, but you also have a rgb
parameter in rect
's parameter list. When inside a parameter list, when referencing a variable name, the interpreter will try to find what the variable name binds to - if the parameter list already has that variable name, it will reference that binding. So new rgb()
is referencing the parameter rgb
, which hasn't been initialized yet.
This isn't exactly what's happening, but the scope of a parameter list looks a bit like if the parameter names are declared with let
, and then assigned values, eg the scope of
const fn = (a, b, c = 'bar') => {
console.log('fn invoked');
};
is similar to:const fn = (argA, argB, argC) => {
let a;
let b;
let c;
a = argA;
b = argB;
c = argC === undefined ? 'bar' : argC;
fnBody(a, b, c);
};
const fnBody = (a, b, c) => {
console.log('fn invoked');
}
So doing rgb = new rgb()
is likelet rgb;
rgb = argRGB === undefined ? new rgb() : argRGB
// ^^^ reference to un-initialized variable
For similar reasons, you can do:const fn = (a, b = a) => { console.log(a, b);};
fn('foo');
Related Topics
Splicing a JavaScript Array from Within the Callback Passed to Foreach
JavaScript Request Fullscreen Is Unreliable
JavaScript Call Nested Function
Convert CSV Data into JSON Format Using JavaScript
Why Does If("String") Evaluate "String" as True But If ("String"==True) Does Not
What Is Event Pooling in React
How to Deeply Merge Two Object Values by Keys
Decompress Gzip and Zlib String in JavaScript
How to Concatenate Regex Literals in JavaScript
Why How to Not Throw Inside a Promise.Catch Handler
Promises for Promises That Are Yet to Be Created Without Using the Deferred [Anti]Pattern
Onclick Event Binding in React.Js
Getusermedia() in Chrome 47 Without Using Https
Google Map API - Multiple Icons in Wrong Spot
Sampling a Random Subset from an Array
How to Randomly Generate HTML Hex Color Codes Using JavaScript