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.
Related
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() ; }
}
Hey so I have a function that takes a string from an input box and splits it up to numbers and letters, seen here:
function sepNsLs() {
"use strict";
var letterArray = [];
var numberArray = [];
separatorSpacerator();
var L = 0;
var listResult = document.getElementById("listInput").value;
var splitResult = listResult.split(separator.sep);
for (; L < splitResult.length; L++) {
if (isNaN(splitResult[L])) {
letterArray.push(splitResult[L]);
} else if (Number(splitResult[L])) {
numberArray.push(splitResult[L]);
}
}
}
My program has to pass through JSLint perfectly, meaning I need to use my functions in strict mode. I've only put them in strict mode now, meaning that my later functions that try to call the letterArray and numberArray that I declared and filled in the SepNsLs function no longer call those arrays and the arrays come up undeclared. Here's the code for one of them:
function addNumbers() {
"use strict";
var sum = 0;
var i = 0;
sepNsLs();
while (i < numberArray.length) {
sum = sum + Number(numberArray[i]);
i++;
}
As you can see, I call the sepNsLs function in the addNumbers function, but because of strict mode, I can't use the arrays sepNsLs creates. How do I fix this? Also, is there a website like the javascript beautifier that will fix my current code to fit strict mode conventions?
EDIT: Separator is declared a global variable here:
var separator = {
sep: 0
};
separatorSpacerator makes it so that if I choose to split my input strings at a space, the input box to tell my program to split at the spaces declares the word "Space" so I can see it is a space I'm splitting my string at.
function separatorSpacerator() {
"use strict";
var list = document.getElementById("listInput").value;
if (document.getElementById("separatorInput").value === "space") {
separator.sep = " ";
} else if (document.getElementById("separatorInput").value === " ") {
separator.sep = " ";
document.getElementById("separatorInput").value = "space";
} else {
separator.sep = document.getElementById("separatorInput").value;
}
if (list.indexOf(separator.sep) === -1) {
alert("Separator not found in list!");
clearResults();
}
}
I can't use the arrays sepNsLs creates. How do I fix this?
One way of fixing this would be to return arrays sepNsLs creates with e.g. a tuple - return [numberArray, letterArray]; , and then use it like:
a) es6 syntax:
var [numberArray, letterArray] = sepNsLs();
b) pre-es6 syntax:
var split = sepNsLs(),
numberArray = split[0],
letterArray = split[1];
Your addNumbers function should also probably return sum - otherwise, it doesn't produce any meaningful results as it stands.
Although not relevant to the question and is more of a matter of naming convention preference - you might want to explore on Hungarian notation and its' drawbacks.
Your problem is one of scope. When you try to access numberArray inside of addNumbers it doesn't exist.
You have a couple of options:
Make all the variables that need to be accessed in each function global.
Wrap all of your functions in an outer function and place the 'global' variables into that outer scope.
The better option is #2, because you won't actually be polluting the global scope with variables. And you can declare "use strict" at the top of the outer function and it will force everything in it into strict mode.
Something like this:
(function() {
"use strict";
// These are now in-scope for all the inner functions, unless redclared
var letterArray = [], numberArray = [], separator = {sep: 0};
function sepNsLs() {
// code goes here
}
function addNumbers(){
// code goes here
}
function separatorSpacerator(){
//code goes here
}
// ...more functions and stuff
// and then call...
theFunctionThatKicksOffTheWholeProgram();
}());
The variables letterArray and numberArray are declared local to the function sepNsLs, they are only accessed in that scope (strict mode or not). Here is an example:
function foo() {
var fooVar = 5;
console.log(fooVar);
}// fooVar get destroyed here
function bar() {
console.log(fooVar); // undefined because fooVar is not defined
}
foo();
bar();
A scope usually is from an open brace { to it's matching close brace }. Any thing declared inside a scope is only used withing that scope. Example 2:
var globalVar = 5; // belongs to the global scope
function foo() {
var fooVar = 6; // belongs to the foo scope
function bar1() {
console.log(globalVar); // will look if there is a globalVar inside the scope of bar1, if not it will look if there is globalVar in the upper scope (foo's scope), if not it will in the global scope.
console.log(fooVar); // same here
var bar1Var = 7; // belongs to bar1 scope
}
function bar2() {
var globalVar = 9; // belongs to the bar2 scope (shadows the global globalVar but not overriding it)
console.log(globalVar); // will look if there is a globalVar in this scope, if not it will look in one-up scope (foo's scope) .... untill the global scope
console.log(bar1Var); // undefined because bar1Var doesn't belong to this scope, neither to foo's scope nor to the global scope
}
bar1();
bar2();
console.log(globalVar); // prints 5 not 9 because the serach will be in foo's and the global scope in that order
}
foo();
What you need to do is to decalre the variables letterArray and numberArray where they can be access by both sepNsLs and addNumbers (one scope above both of them). Or return the value from sepNsLs and store it in a variable inside addNumbers. Like this:
function sepNsLs() {
// logic here
return numberArray; // return the array to be used inside addNumbers
}
function addNumbers() {
var arr = sepNsLs(); // store the returned value inside arr
for(var i = 0; i < arr.length; ... // work with arr
}
I have a for loop where I get the start number for my countdown. I want to use that number outside the for loop and here comes the closure problem.
for (var i = 0; i < response.length; i++) {
var number = response[i].number; //the number is 10
getNumber(number);
};
So I thought I should call a function that returns that number so I can use it somewhere else:
function getNumber(number) {
return number;
}
But when I try to do this, I get an undefined instead of 10:
var globalVariableForNumber = getNumber();
What I know I am doing wrong is calling getNumber() without the parameter when assigning the value to my variable, but how else should I do it?
The number comes from an ajax call that has more numbers in it (response[i].number). I then want to use those numbers to be the start timers of my countdown. So if the number is 10, then my countdown will start from 10.
Thank you.
var response = [
{number:5},
{number:6},
{number:7},
{number:8},
{number:9},
{number:10}
]
var number;
for (var i = 0; i < response.length; i++) {
number = response[i].number; //the number is 10
console.log(getNumber());
};
function getNumber() {
return number;
}
// try it again later...
console.log(getNumber());
Again, you should just be calling number directly. But for the purpose of this question you have to declare number in a higher scope.
Scope
The current context of execution. The context in which values and
expressions are "visible," or can be referenced. If a variable or
other expression is not "in the current scope," then it is unavailable
for use. Scopes can also be layered in a hierarchy, so that child
scopes have access to parent scopes, but not vice versa.
A function serves as a closure in JavaScript, and thus creates a
scope, so that (for example) a variable defined exclusively within the
function cannot be accessed from outside the function or within other
functions.
Consider this example of scopes that your code could be declared and called:
function ImInGlobalScope(){ //Function declared in global scope
//Lets call this block 1
for (var i = 0; i < response.length; i++) {
var number = response[i].number; //Inside function scope
getNumber(number)
};
//Lets call this block 2
function getNumber(number){ //In same scope than for statement
return number; //block 3
}
}
var globalVariableForNumber = getNumber();
With this example getNumber is undefined because it belongs to ImInGlobalScope() scope. Consider another scenario of scopes for your code.
function ImInGlobalScope(){ //Function declared in global scope
//Lets call this block 1
for (var i = 0; i < response.length; i++) {
var number = response[i].number; //Inside function scope
getNumber(number)
};
}
//Lets call this block 2
function getNumber(number){ //In same scope than for statement
return number; //block 3
}
var globalVariableForNumber = getNumber();
I believe the above is what your scenario is: getNumber is on global scope but number is ImInGlobalScope().
So when you're calling var globalVariableForNumber = getNumber(); We have the following:
function getNumber(number){ //Number is not being passed so is undefined
return number; //no variable named number exists in this scope so it will return undefined.
}
What you are doing is creating a function that takes exactly one parameter and returns that parameter when it is called. Return is a fancy way of saying that if you say foo=bar() then foo is whatever bar() returned.
In your code, calling getNumber() with no parameter returns undefined because it just returns the parameter. What you should do instead is not return the parameter, but instead set the global variable to it like so:
function getNumber(number) {
globalVariableForNumber = number;
}
Now, just get the number by running:
globalVariableForNumber
var a=1; //first one
function x() {
var a=2; // second
function y() {
var a=3; // third one
}
}
Is there any way that function y() can access the second var a? I know it can access first one via window.a.
As-written?
No.
If you're not dead-set on naming each one a, then you can easily reference it.
The other solution would be to capture the outside variable within another variable, the trick being to not have the same variable name being referenced in the outside scope, from the inside scope.
window.a = 1;
function x() {
var a = 2,
inner_a = a,
y = function () {
var old_a = inner_a,
// a is equal to the closest var assignment ie: inside x()
a = 3;
};
}
or to pass it into the construction of a new function through closure
(immediately-invoking function)
window.a = 1;
function x() {
var a = 2;
var y = (function (old_a) {
return function () { var a = 3; };
// this inner function has access to "old_a", through closure
}(a));
}
This is a pattern that is preferred for several use-cases, when mixing JS with browser-functionality (ie: the DOM and DOM events, loops which assign timers or callbacks, AJAX responses, et cetera).
Is it possible to do what I want? the changeEl() function is also in fadeGall()
function initOpen(){
$('ul.piple-holder > li > a, ul.work-holder > li > a').each(function(){
var _box = $(this);
_box.click(function(){
//SOME CODE HERE TO RUN changeEl(0); on each _hold
//element from fadeGall()
});
});
}
function fadeGall(){
var _hold = $('div.work-info');
_hold.each(function(){
var _hold = $(this);
function changeEl(_ind){
return;
}
});
}
No idea what you're trying to do, but if you are asking if it's okay to define a function inside of another one, sure, it is. Just be aware that the nested function won't be callable outside of the function it was defined in.
You can nest a function within a function. The nested (inner) function is private to its containing (outer) function. It also forms a closure.
A closure is an expression (typically
a function) that can have free
variables together with an environment
that binds those variables (that
"closes" the expression).
reference
Since a nested function is a closure, this means that a nested function can "inherit" the arguments and variables of its containing function. In other words, the inner function contains the scope of the outer function.
To summarize:
The inner function can be accessed only from statements in the outer function.
The inner function forms a closure: the inner function can use the arguments and variables of the outer function, while the outer function cannot use the arguments and variables of the inner function.
The following example shows nested functions:
function addSquares(a,b) {
function square(x) {
return x * x;
}
return square(a) + square(b);
}
a = addSquares(2,3); // returns 13
b = addSquares(3,4); // returns 25
c = addSquares(4,5); // returns 41
Since the inner function forms a closure, you can call the outer function and specify arguments for both the outer and inner function:
function outside(x) {
function inside(y) {
return x + y;
}
return inside;
}
fn_inside = outside(3);
result = fn_inside(5); // returns 8
result1 = outside(3)(5); // returns 8