I've been finding some weird behaviours in my code and finally tracked it down to what I considered impossible.
A variable "i" used in a for loop is changed within an enclosing for loop on another function.
In this case function a only logs 0 and function b 0-9. The behaviour I wanted was for both functions to log 0-9.
I solved the problem in my code changing the variable name 'i1' & 'i2'. But I'm guessing there is a more elegant solution.
function a() {
for (i=0;i<10;i++) { // Changed to i1
console.log('a',i);
b() ; }
}
function b() {
for (i=0;i<10;i++) { // Changed to i2
console.log('b',i);
}
}
a() ;
Use let when declaring your local variables. Otherwise, as others have mentioned, i becomes global.
function a() {
for (let i=0;i<10;i++) {
console.log('a', i);
b(); }
}
function b() {
for (let i=0;i<10;i++) {
console.log('b', i);
}
}
a();
Tip: It is generally better practice to use let over var. It will help you limit the scope of your local variables. In the example above, using let limits the scope of i to within the loop, whereas var would set the entire function as the scope (try using var and printing the value of i after the loop).
You are adding i to the Global window object, this will behave as you expect:
function a() {
for (var i=0;i<10;i++) { // Changed to i1
console.log('a',i);
b() ; }
}
function b() {
for (var i=0;i<10;i++) { // Changed to i2
console.log('b',i);
}
}
a();
Place 'use strict'; at the top of every Javascript file and you will never have that kind of strangeness happen again. Place 'use strict' at the top of your Javascript now. You will see that there is an error at both i's because you are declaring them with var keyword.
When you started you declared i in your first for loop (i=0;i<10;i++) you put i in the global scope: window.i. Thus both for loops are using the same window.i variable.
The second for loop in function b does not declare a new i variable, the global i variable is used because it was created in function a.
There is a few issues. One is issue the variable i should be declared in the for loop:
for (i=0;i<10;i++)
should be changed to
for (let i=0;i<10;i++)
The other change to make is the b function should be called outside the loop. The full solution would be:
function a() {
for (let i=0;i<10;i++) {
console.log('a',i);
}
b();
}
function b() {
for (let i=0;i<10;i++) {
console.log('b',i);
}
}
a();
the way you are using i causes javascript to consider it as a global variable. Other languages might have the i variable be local to the for loop's scope by default, but javascript doesn't work this way. Javascript is designed so variables are global by default. You can limit a variables scope to a function with var, such as:
function a() {
for (var i=0;i<10;i++) {
console.log('a',i);
b() ; }
}
Please note that this scopes i to the function, not the for loop as you may want. You can use the newer let keyword to define variables with more granular scoping (as of ES2015). Here is an example of the i variable being scoped to the for loop only (not the function):
function a() {
for (let i=0;i<10;i++) {
console.log('a',i);
b() ; }
}
Related
If I define an inner function inside a function, the inner function has access to the outer function's variables. If I want this inner function to be reusable and define it outside the outer function, the inner function now loses access to the outer function variables. How do I make this new reusable inner function have access to outside function variables, without passing those variables in as parameters?
function a () {
var x = [[1,2,3], [1,2,3], [1,2,3]];
var keys = Object.keys(x[0]);
for (var i = 0; i < x.length; i++) {
angular.forEach(keys, loop);
}
}
function loop (key) {
console.log(key, i);//i is undefined here
}
a();
Specifically, is there some way without 1) assigning variables to this, 2) without passing in variables as parameters, and 3) without creating global variables?
Edit: It seems there is no way to do this. But if I try another approach, to have the reusable function return a new function, I also do not have access to the inner scope. Why is this, and is there some way to make this work?
function a () {
var x = [[1,2,3], [1,2,3], [1,2,3]];
var keys = Object.keys(x[0]);
var myloop = loop();
for (var i = 0; i < x.length; i++) {
angular.forEach(keys, myloop);
}
}
function loop (key) {
return function(key) {
console.log(key, i);//i is undefined here
};
}
a();
In the following example, loop returns a function that closes over the value of i.
function a () {
var x = [[1,2,3], [1,2,3], [1,2,3]];
var keys = Object.keys(x[0]);
for (var i = 0; i < keys.length; i++) {
keys.forEach(loop(i));
}
}
function loop (i) {
return function (key) {
console.log(key, i); // i is now defined
}
}
a();
Output:
0 0
1 0
2 0
0 1
1 1
2 1
0 2
1 2
2 2
How do I make this new reusable inner function have access to outside function variables, without passing those variables in as parameters?
You can't. JavaScript has lexical scope, not dynamic scope.
See also: What is lexical scope?
I also want to make another option known which I just discovered. If you use .bind, you can curry the function with i, and the other variables will be passed in after the curried parameters.
....
angular.forEach(keys, loop.bind(null, i));
...
function loop(i, key) {
...
}
Inner functions are treated locally by the outer function. Therefore, you can access the variables belonging to the outer function from the inner function. But, once you have the inner function as a separate function outside the outer function, then you no longer have access to the private data variables of the outer function.
If this seems complicated, here is an example:
function A
{
//variables
function B
{
can use variables of A
}
}
But,
function A
{
//variables
}
function B
{
//cannot access variables of A
}
In JS (and many other languages), there is a visibility context. Possible contexts are e.g. "global" or function or block. These contexts are hierarchical, inner can read outer. Outer can never read inner (encapsulation principle) unless inner declares variable as global.
Can anyone explain why
function x() {
console.log("Hello!");
}
var a = x;
a();
x();
produces
Hello!
Hello!
but this
var a = function x() {
console.log("Hello!");
}
a();
x();
throws an error when you try to call function x? Is the second x function not considered a hoisted function? I tried this in both nodejs and a browser.
What you have in the second example is what's called a named function expression.
Its name is not added to the containing scope, but is accessible within the scope of the function itself:
var a = function x() {
alert(x);
};
a();
This is useful in writing recursive functions or functions that otherwise reference themselves, as it ensures that the name won't get clobbered due to anything that happens outside the function's scope.
It also allows you to create self-referencing functions in places where you can't use a function declaration, such as in an object literal:
var myFavoriteFunctions = {
factorial: function f(n) {
return n === 1 ? 1 : n * f(n);
},
identity: function (v) { return v; }
};
console.log(myFavoriteFunctions.factorial(10));
Your first example is a function statement, which declares a name in its containing scope.
Your second example is a named function expression, which does not.
For more information, see here.
From what i know, a function say A defined within another function say B has access to local variables of B as well.
function B() {
var x = 10;
function A() {
console.log(x); //will result in 10
var y = 5;
}
console.log(y); //ReferenceError: y is not defined
}
However in the below example y gets printed. I know there is no such thing as block scope in javascript for "if block" but shouldn a declaration atleast be invisible outside of "if" i mean shouldnt var y be limited to if block?
function B() {
var x = 10;
if(1) {
console.log(x); //will result in 10
var y = 5;
}
console.log(y); will result in 5
}
JavaScript isn't truly block scoped. An if or for loop does not have its own scope. All variable declarations in any given scope (that is: global scope, or a function) are hoisted, and this visible anywhere inside that scope.
ECMAScript6 (Harmony) will, in all likelihood, introduce block-scope to JS, through the use of the new let keyword see the wiki:
for (let i=0;i<12;++i)
{
console.log(i);//logs i
}
console.log(i);//reference error
There also seems to be some confusion as far as terminology is concerned here: a closure is not the same as a scope.
The snippet above is an example of block-scoped code. The variable i is created at the start of the loop, and simply ceases to exist once the loop finishes. It is GC'ed (Garbage Collected). i is no more, bereft of life it rests in peace. Closures are something different entirely: they rely on variables that are not GC'ed, but can't be accessed from outside code. They can only be accessed through the return value of the closure:
var someClosure = (function()
{//this function creates a scope
var x = 123;
return {
getX: function()
{
return x;
},
setX: function(val)
{
return x = val;
}
};
}());
console.log(x);//reference error
console.log(someClosure.getX());//returns 123, so "x" still exists
That being said:
You can mimic a block-scoped loop through the use of a closure, though. Functions are scoped, and a closure is a function or object that is returned by a function. That means that the return value of a function has access to the entire function's scope. See this answer for details.
Apply what is explained there, and work out why this loop is "pseudo-scoped":
var foo = [1,2,3,4,5,6],
largerThan3 = (function(arr)
{
var resultArr = [];
for (var i=0;i<arr.length;++i)
{
if (arr[i] > 3)
resultArr.push(arr[i]);
}
return resultArr;
}(foo));
console.log(i);//reference error...
nope.
as you said - if blocks does not have their own closures in JS - which means everything is part of the outer closure (in this case - y is a local variable in B's closure). so it will be completely visible in B's body
So I want to call function B in function A, but function B is fully declared after function A. I know that in c++ we'd use function prototypes on B, but what about javascript?
code:
markerArray = function() {
// some code here
this.array = [];
this.clearArray = function() {
for(var i = 0; i<this.getLength(); i++)
// for loop code
}
this.getLength = function() {
return this.array.length;
}
// some code here
}
these reason why I put this.getLength below is mainly because my coding style/structure is more readable this way
Javascript doesn't care about this requirement. It will simply work as long as Function A isn't called until after the file is loaded. Function A will be defined, Function B will be defined, then Function A can be called using Function B inside of it with no problem.
Not a problem. Function declarations are hoisted to the top of the enclosing variable environment, so they do not need to be declared in order.
A();
function A() {
B();
}
function B() {
alert('B was called');
}
If you meant something else, you'll need to explain it in your question.
It depends on how you declare your functions. If you declare a function via the Function constructor or a function expression, order matters.
a(1); //this call won't work
//function expression of an anonymous function assigned to the variable multiply
var a = function(i) {
b(i);
}
// b is defined using Function constructor
var b = new Function("i","alert('B was called with ' + i)");
a(1); //this call will work
My attempts at giving global scope to a nested JavaScript function are not working:
//DECLARE FUNCTION B IN GLOBAL SCOPE
function B;
function A() {
//DEFINE FUNCTION B INSIDE NEST
B() {
alert("function B is running");
}
}
//CALL FUNCTION B FROM GLOBAL SCOPE
B();
This is just curiosity -- you are correct that I don't really have any good reason to want to do this.
TIA -- I don't have an SO account to respond to your answers...
function B; will simply generate a syntax error.
You can use a function expression. As functions are first class objects, you can assign a function to a variable:
var B; // declare (global) variable (outer scope)
function A() {
// assign a function to it
B = function() {
alert("function B is running");
};
}
// we have to call A otherwise it won't work anyway
A();
// call B
B();
You could also let A return a function:
function A() {
return function() {
alert("function B is running");
};
}
B = A();
This would make the relation between A and B a bit clearer.
Of course you can always define a global variable by omitting var, but you should use this very carefully. Use as less global variables as possible.
function A() {
B = function() {
alert("function B is running");
};
}
And I bet there is a better way of doing it, depending on what your actual goal is.
More about Functions and function scope.
What about:
function A() {
window.B = function() {
alert("function B is running");
}
}
//CALL FUNCTION B FROM GLOBAL SCOPE
B();
You can do something like this:
function outer() {
function inner() {
// ..
}
window['inner'] = inner;
}
It's a little icky to have a direct reference to "window", so you could do this (from the global context):
(function (global) {
function inner() {
// code code code ...
}
global['inner'] = inner;
})(this);
There appear to be a couple of issues with your code
The first line doesn't appear to be legal Javascript (JSLint agrees). To declare an uninitialized variable use the var B; syntax
The code never calls A to initialize B so calling B() is invoking an uninitialized variable
I'm fairly certain the code to initialize B inside of A is also not legal.
Try the following
var B; // Establish B as a global scope variable
function A() {
B = function() {
alert('B is running');
};
}
A(); // Call the function which will cause B to be initialized
B(); // Run B
You're close, but not completely correct.
You have to define B as a variable and then assign a function to it.
Also run A() before executing B, otherwise B will be undefined. The easiest way of running it is the way that I show in my code example.
These are the smallest amount of changes to your code to make it work as you asked:
var B;
(function A() {
// define function B
B = function() {
alert("function B is running");
}
})();
// call function B globally
B();