JS: Default function parameter values and scope - javascript

I'm a bit confused about how scope & variable assignment within a function seems to change when assigning a default parameter value for that function.
For example, when this function has a default value assigned to parameter i, the output array variable appears to be block-scoped when inspected with Chrome Dev Console,:
function steps(n, i = 40) {
var output = [n];
}
steps(10, 20);
However, by removing the default parameter value for i, the output array variable is scoped locally:
function steps(n, i) {
var output = [n];
}
steps(10, 20);
Why does assigning a default value to parameter i affect the scope of the output array variable?
I was initially made aware of this shift in function scope by attempting to run the following segment of code through pythontutor.com's live programming environment for Javascript. Even though the code executes as expected within an IDE, it fails to run due to scope issues on pythontutor:
function steps(n, i = 1) {
// declare base case
if (n === 0)
return;
var output = [];
print(i, "#");
print(n - 1, " ");
console.log(output.join(""));
// make recursive call
steps(n - 1, i + 1);
function print(num, char) {
for (let j = 0; j < num; j++) {
output.push(`${char}`);
}
}
}
steps(3);
The pythontutor processor halts execution three steps in, at the invocation of print() just after declaring the output variable. Pythontutor.com will, however, execute the code as expected if I declare the output variable globally first:
var output = [];
function steps(n, i = 1) {
// declare base case
if (n === 0)
return;
output = [];
print(i, "#");
print(n - 1, " ");
console.log(output.join(""));
// make recursive call
steps(n - 1, i + 1);
function print(num, char) {
for (let j = 0; j < num; j++) {
output.push(`${char}`);
}
}
}
steps(3);

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

Related

Weird function behaviour

I have a simple function:
var k = 10;
function getNumber(k){
if ((1<2)&&(k===10)) {
k = 11;
return 4;
}
return 2;
}
console.log(getNumber())
console.log(getNumber())
Why does it always give 2, ignorning if ()?
Parameter k is scoped to the function, remove that:
var k = 10;
function getNumber(){
if ((1<2)&&(k===10)) {
k = 11;
return 4;
}
return 2;
}
console.log(getNumber())
console.log(getNumber())
k variable is not given to getNumber().
In function scope, parameter is undefined
Why does it always give 2, ignorning if ()?
It doesn't ignore the if() its just the the condition is false - which can be checked using a console.log
var k = 10;
function getNumber(k){
console.log(k, k === 10);
if ((1<2)&&(k===10)) {
k = 11;
return 4;
}
return 2;
}
console.log(getNumber())
console.log(getNumber())
As you can see, k inside the function is undefined and as this variable is local to the function the k in outer scope is not the value you're working with that you expected to check by setting it to 11.
See the comments in the code
// This is a global variable. Similar to
// window.k = 10;
var k = 10;
// By setting k as parameter to the function you define it as
// a local variable in the function scope
// It's somewhat similar to this line
// var k = arguments[0];
// Since you call getNumber() without arguments, k is undefined in this scope
function getNumber(k) {
// the test for the k value is against the local value,
// which is undefined and not 10
if ((1 < 2) && (k === 10)) {
// This code is never executed
k = 11;
return 4;
}
// Always returns 2
return 2;
}
console.log(getNumber())
console.log(getNumber())
To make your code work, do this
var k = 10;
// function parameter with default value if argument is not given
function getNumber(k = window.k) {
if ((1 < 2) && (k === 10)) {
// set global k instead of local
window.k = 11;
return 4;
}
return 2;
}
console.log(getNumber())
console.log(getNumber())
BTW This is not a code strategy that I'd recommend. Global variables should be avoided, especially the use or change of global variables inside functions. Since I regard this as a demonstration code and not real life code, my code correction is for this demonstration purpose alone and should not be used in production code.

Initializing, adding to, saving a local variable within a function, and accessing that variable with the function

var jFunc = function() {
var q;
var p = 0;
q = (typeof(q) === "undefined") ? 0 : q;
p += 1;
q += p;
console.log(q);
};
In the console of chrome developer tools, the output of console.log(q) is q + 1 (1, 2, 3, 4, and so on..) every subsequent iteration of running the entire block of code within jFunc independently of invoking jFunc. However, when invoking jFunc (so as to conveniently avoid copying and pasting the code block within the console), the output is always 1 everytime. Why is this the case?
The goal is to essentially save a numeric value to a variable, add 1 to it, and return it without the use of global variables.
What you need is a function with its own persistent scope to keep track of the counter. You can use an IIFE while defining jFunc:
const jFunc = (() => {
let p = 0;
return () => {
p++;
return p;
}
})();
console.log(jFunc());
console.log(jFunc());
console.log(jFunc());
This can be simplified a bit by just returning the ++, but it's a bit more confusing if you're not familiar with it:
const jFunc = (() => {
let p = 0;
return () => ++p;
})();
console.log(jFunc());
console.log(jFunc());
console.log(jFunc());
Or, if you need to create multiple counter functions, instead define a higher-order function that can create a counting function when called:
const makeJFunc = () => {
let p = 0;
return () => {
p++;
return p;
}
};
const jFunc1 = makeJFunc();
const jFunc2 = makeJFunc();
console.log(jFunc1());
console.log(jFunc1());
console.log(jFunc2());
console.log(jFunc1());
Probably you are looking for Closures
You have to return your repeating functionality as a function so that only that part can be invoked. Rest will be declared only once and will be local to that function scope.
var jFunc = (function() {
var q;
var p = 0;
return function(){
q = (typeof(q) === "undefined") ? 0 : q;
p += 1;
q += p;
console.log('q',q);
return q;
}
})();
jFunc();
jFunc();
jFunc();

javascript for-loop and timeout function

I am facing an issue with a for-loop running a setTimeout.
for (var x = 0; x < 5; x++) {
var timeoutFunction = function() {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(), 1)
}
I expect the output
0
1
3
4
Yet, for some reason they all output 5.
Variable x is defined in the local scope of the for-loop, so I thought this may not count for the callback of setTimeout. I tested with defining x outside of the for-loop.
var x = 10
for (var x = 0; x < 5; x++) {
var timeoutFunction = function() {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(), 1)
}
I figured this output would be giving 10, yet it didn't. Then I thought it would make sense to define x afterwards.
for (var x = 0; x < 5; x++) {
var timeoutFunction = function() {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(), 1)
}
var x = 10
This does return only 10. This implies the callbacks are all called after the for-loop is executed? And why do they only conform to the parent scope of the for-loop once the variable is initialised after execution of the for-loop? Am I missing something?
I know how make this example working with
for (var x = 0; x < 5; x++) {
var timeoutFunction = function(x) {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(x), 1)
}
Yet, I really wonder what is missing...
Note that specifying 1 as the delay value will not actually cause the function to execute after 1 millisecond. The fastest the function could execute is roughly 9 milliseconds (and that's based on the internals of the browser), but in reality, it can't run the function until there is no other code running.
As for the unexpected output, you are experiencing this behavior because the timeoutFunction includes a reference to the x variable that is declared in a higher scope. This causes a closure to be created around the x variable, such that each time the function runs it does not get a copy of x for itself, but it is sharing the x value because it is declared in a higher scope. This is the classic side effect of closures.
There are a few ways to adjust the syntax to fix the issue...
Make a copy of x and let each function use its own copy by passing x into the function. When you pass a primitive type (boolean, number, string) a copy of the data is created and that's what is passed. This breaks the dependence on the shared x scope. Your last example does this:
for (var x = 0; x < 5; x++) {
var timeoutFunction = function(x) {
return function() {
console.log(x)
}
}
// Because you are passing x into the function, a copy of x will be made for the
// function that is returned, that copy will be independent of x and a copy will
// be made upon each loop iteration.
setTimeout(timeoutFunction(x), 1)
}
Another example that does the same thing would be needed if your timeout function wasn't returning another function (because there would be no function to pass the value to). So, this example creates an extra function:
for (var x = 0; x < 5; x++) {
// This time there is no nested function that will be returned,
function timeoutFunction(i) {
console.log(i);
}
// If we create an "Immediately Invoked Funtion Expression" (IIFE),
// we can have it pass a copy of x into the function it invokes, thus
// creating a copy that will be in a different scope than x.
(function(i){
setTimeout(function(){
timeoutFunction(i); // i is now a copy of x
}, 1);
}(x));
}
If you are working with browsers that support the ECMAScript 2015 standard, you can simply change the var x declaration in your loop to let x, so that x gets block level scope upon each iteration of the loop:
// Declaring a variable with let causes the variable to have "block-level"
// scope. In this case the block is the loop's contents and for each iteration of the
// loop. Upon each iteration, a new "x" will be created, so a different scope from
// the old "x" is what's used.
for (let x = 0; x < 5; x++) {
var timeoutFunction = function() {
return function() {
console.log(x)
}
}
setTimeout(timeoutFunction(), 1)
}
You have to create new scope for your function to make it 'remember' value from given iteration:
for (var x = 0; x < 5; x++) {
var timeoutFunction = (function(x) {
return function() {
console.log(x)
}
})(x)
setTimeout(timeoutFunction(), 1)
}
Another solution is to use ES2015 let:
for (let x = 0; x < 5; x++) {
var timeoutFunction = function() {
console.log(x)
}
setTimeout(timeoutFunction(), 1)
}
use this snippet
for(let x = 0; x < 5; x++) {
var timeoutFunction = function() {
console.log(x);
};
setTimeout(timeoutFunction, 1);
};
console.log('For loop completed');
JavaScript is not multi threaded so with your code the timeoutFunction will execute after the for loop is completed since you are using the global variable (with respect to timeoutFunction context) the final result is printed

Issue getting closures to work [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I have a piece of code that I'm trying to have alert 1,2,3. I'm having issues using closures properly, so I can't figure this out.
The original code:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
I am trying to do something like this to buildList() to get it to work properly:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result[i] = function(x) {
result.push( function() {alert(item + ' ' + list[x])} );
}(i);
}
return result;
}
I know I'm making mistakes on working with the closures, I'm just not sure what the problem is.
Your second try was closer to the solution but still doesn't work because your inner-most function is capturing variable item from your top-level function: item is just always referencing the same instance, which was created when calling buildList().
var scope in JavaScript is always bound to current function call, not to code block, so it's not bound to control statements like for.
For that reason, the alerts likely show the value 'item' + (list.length-1) had at the time of calling buildList().
Since you are passing i to your closure, you should declare var item within that function, e.g:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
result[i] = function(x) {
// x and item are both local variables of anonymous function declared just above
var item = 'item' + list[x]; // or maybe you meant 'item' + x?
return function() {alert(item + ' ' + list[x])};
}(i);
}
return result;
}
Note that the closure would still capture a reference to list so will display the value it contains at the time of calling functions in the array returned by buildList(). Also local variable item is completely optional, you could call alert('item' + x /*or is it list[x]?*/ + ' ' + list[x]).
From How do JavaScript closures work?
Note that when you run the example, "item2 undefined" is alerted three
times! This is because just like previous examples, there is only one
closure for the local variables for buildList. When the anonymous
functions are called on the line fnlistj; they all use the same
single closure, and they use the current value for i and item within
that one closure (where i has a value of 3 because the loop had
completed, and item has a value of 'item2'). Note we are indexing from
0 hence item has a value of item2. And the i++ will increment i to the
value 3.
You need to make a closure in each loop iteration if you are to store the matching value of i:
function buildList(list) {
var result = [], item, closure;
for (var i = 0; i < list.length; i++) {
item = 'item' + list[i];
// call this function with the string you wish to store
// the inner function will keep a reference to the 'msg' parameter even after the parent function returns
closure = (function(msg) {
return function() {
alert(msg);
};
}(item + ' ' + list[i]));
result.push( closure );
}
return result;
}
function testList() {
var fnlist = buildList([1, 2, 3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
Same question asked here and here. Same answers here, here, here, here and probably in dozen more places.

callback function within a loop

I am really struggling with concept of scope in my code.
I am simply trying to create a 'callback' function which will add a className to a variable. As it's inside a function, I am passing the global variable as parameters to the callback function using the concept of closure (still dont understand how closure works).
var ePressCuttingsArray = $(".cPressCuttings");
var eSelectedPressCuttingsArray = [];
var iIndexArray = [];
for (var i = 0; i < 7; i++) {
var iIndexArrayValue;
// two conditions being checked in while loop, if random no. is not in global array (iIndexArray) & i var is equal to eSelectedPress... array
while (jQuery.inArray(((iIndexArrayValue = Math.floor(Math.random() * 14) + 1), iIndexArray) === -1)
&& (i === eSelectedPressCuttingsArray.length))
{
// to push a value at a position from array ePressCut... into eSelectedPress... array
eSelectedPressCuttingsArray.push(ePressCuttingsArray[iIndexArrayValue]);
// run a function to addClass to the recently pushed value in eSelectedPress... array
(function (i) {
$(eSelectedPressCuttingsArray[i]).addClass("cPressCuttingsDisplay0" + i)
} (i) );
iIndexArray.push(iIndexArrayValue);
}
}
Could someone explain why the closure func. is not performing correctly, i.e. it always successfully add the className "cPressCuttingsDisplay00", but doesnt follow that up with a className of "cPressCuttingsDisplay01" for the next loop iteration.
You should be able to accomplish your goal by using a for loop:
var ePressCuttingsArray = $(".cPressCuttings").makeArray();
var eSelectedPressCuttingsArray = [];
for (var i = 0; i < 7; i++) {
var idx = Math.floor(Math.random() * ePressCuttingsArray.length);
var selectedItem = ePressCuttingsArray[idx];
selectedItem.addClass('cPressCuttingsDisplay0' + i);
eSelectedPressCuttingsArray.push(selectedItem);
ePressCuttingsArray.splice(idx, 1);
}

Categories

Resources