Scope of Default Function Parameters in JavaScript

Scope of Default function parameters in javascript

I don't understand why these examples are not good

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 like

(function(a = 2, b = a) { console.log(b); }());

Could somebody please point me to place in specification which could clarify this?

The relevant section is §9.2.12 FunctionDeclarationInstantiation.

I must say that specification is rather hard to understand.

Yes it is, although it's written for engine implementors not for programmers. However, the explanatory note basically confirms your understanding of the optimisation

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];
}

and

function 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 x

try 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 again

function 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:

  1. Global - visible by everything
  2. Function - visible within a function (and its sub-functions and blocks)
  3. Block - visible within a block (and its sub-blocks)
  4. Module - visible within a module

Outside of the special cases of global and module scope, variables are declared using 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:

  1. How an identifier was declared
  2. Where an identifier was declared
  3. Whether you are in strict mode or non-strict mode

Some of the ways identifiers can be declared:

  1. var, let and const
  2. Function parameters
  3. Catch block parameter
  4. Function declarations
  5. Named function expressions
  6. Implicitly defined properties on the global object (i.e., missing out var in non-strict mode)
  7. import statements
  8. eval

Some of the locations identifiers can be declared:

  1. Global context
  2. Function body
  3. Ordinary block
  4. The top of a control structure (e.g., loop, if, while, etc.)
  5. Control structure body
  6. Modules

Declaration Styles

var

Identifiers declared using var 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 using let 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 like

let 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



Leave a reply



Submit