Is it bad to duplicate declaring JavaScript variables? For instance, given the below code, is one approach better than the other? If so, please explain why. Thank you
function func1() {
for (var i = 0; i < 100; i++) {
var myVar=123;
}
}
OR
function func2() {
var myVar;
for (var i = 0; i < 100; i++) {
myVar=123;
}
}
Actually, these code samples are equivalent and will probably compile to the same bytecode.
function func1() {
for (var i = 0; i < 100; i++) {
var myVar=123;
}
}
AND
function func2() {
var myVar;
for (var i = 0; i < 100; i++) {
myVar=123;
}
}
This is because of hoisting, and in fact you could also do:
function func3() {
var i, myVar;
for (i = 0; i < 100; i++) {
myVar=123;
}
}
Although you might save time with function func4() { var myVar=123; } ;)
These are functionally identical.
Javascript takes two passes through code. During the first pass variables are set up (amongst other things).
In your first version, during the first pass of the interpreter it will see that you declare a variable myVar and it will hoist the definition of the variable to the top of the scope (which is the function in this case because loops don't have their own scope in javascript).
Thus in javascript's second pass, when the code is executed (interpreted), everything will be identical.
So, in javascript all variables act as if they were declared at the top of the scope, regardless of where, or if, you declare them.
One of the potentially confusing things about javascript is that it has c like syntax and yet has some significant differences from other c-like languages. And this is one of those differences.
It's generally considered better to declare variables at the beginning of a piece of code (or function), than at random places. One exception being the for-loop.
function func2() {
var myVar; // try to declare all variables here
for (var i = 0; i < 100; i++) { // except for the for-loop variable
myVar = 123;
}
}
Why I'd do this: because other languages work this way as well, so it makes it easier to read. For JavaScript specificity it doesn't really matter since scope works weird in JavaScript.
Main argument for this way of writing the function: readability.
Related
EDIT: The context for this question is, the argument is the source string for a module being loaded which could be huge, and there are many many modules being loaded this way, and each one has a closure with the original source code of the module held within it, using memory, when its not needed or wanted.
Specifically, I am trying to fix this https://github.com/dojo/dojo/blob/master/dojo.js#L367 code which leaks the module source into the closure.
<script>
function closureTest(n) {
function eval_(__text) {
return eval(__text);
}
for (var i = 0; i < n; i++) {
var m = eval_("(function(){ var x = 10; return function(n) { return x+n; }; })(window);");
m(5);
}
}
</script>
<button onclick="closureTest(1000)">Run</button>
In the above code, if a breakpoint is placed inside the anonymous function, and the closures that exist are examined, we can see that one of the closures contains __text and arguments[0] that contains the original source code of the module as it was passed to eval_
Here is a variation of the above:
<script>
function closureTest(n) {
function eval_() {
return eval(arguments[0]);
}
for (var i = 0; i < n; i++) {
var m = eval_("(function(){ var x = 10; return function(n) { return x+n; }; })(window);");
m(5);
}
}
</script>
<button onclick="closureTest(1000)">Run</button>
In this case, the closure no longer contains __text but does still contain arguments[0] with the string passed to eval_.
About the best I could come up with is the following, which deletes the argument to eval_ after processing it, a side effect is that the module being defined now also appears in the closure as a variable called module.
<script>
function closureTest(n) {
function eval_() {
var module = eval(arguments[0]);
delete arguments[0];
return module;
}
for (var i = 0; i < n; i++) {
var m = eval_("(function(){ var x = 10; return function(n) { return x+n; }; })(window);");
m(5);
}
}
</script>
<button onclick="closureTest(1000)">Run</button>
Is there a better way to prevent the closure retaining a copy of the argument passed to eval_?
[EDIT] Completely missed the point of the question.
Short answer: You cannot. As you are aware, a closure maintains the environment state from which it was created; there's no way to "break out" of a closure scope hierarchy.
When you call m() you are not calling it within a global context, but rather part of a chain of environments of, not only the eval_ closure but also closureTest as well -- You can see your returned eval'ed function also has access to the i and n vars defined in closureTest.
So, you cannot restrict or break out of a closure. However, if you define a new variable in your new scope with the same name as a variable from a previous closure you lose direct access to that. Therfore, your middle example is as close as you can get:
function closureTest(n) {
function eval_() {
return eval(arguments[0]);
}
for (var i = 0; i < n; i++) {
var m = eval_("(function(){ var x = 10; return function(n) { return x+n; }; })(window);");
m(5);
}
}
Here, your returned eval'ed function cannot access eval_'s arguments since it's own arguments have overridden it (it does, however, still have access to i and n from the for loop). This means, functionally, that eval'ed string cannot access the arguments passed to eval_.
Notes:
Yes, when you set a breakpoint in DevTools you can inspect the parent closures however that does not impact the fact that the executing function cannot directly access eval_'s `arguments[0].
Also, the function could get the string of the upper functions using arguments.callee.caller.toString(); etc. This still doesn't allow the function direct access to redefined vars, but thought it was worth mentioning.
I am a C# developer and used to the way closures work in C#.
Currently I have to work with anonymous javascript functions and experience a problem with the following snippet:
function ClosureTest() {
var funcArray = new Array();
var i = 0;
while (i < 2) {
var contextCopy = i;
funcArray[i] = function() { alert(contextCopy); return false; };
i++;
}
funcArray[0]();
funcArray[1]();
}
I expect the first funcArray() call to say 0 and the second to say 1. However, they both say 1. How is that possible?
By writing var contextCopy = i I make sure that I create a copy of the i-variable. Then, in each while-iteration I create a completely new function pointer. Each function refers to its own copy of i, which is contextCopy. However, both created functions for some reason refer to the same contextCopy-variable.
How does this work in javascript?
JavaScript has lexical closures, not block closures. Even though you are assigning i to contextCopy, contextCopy is, itself, a lexical member of ClosureTest (which is different from C#, where the {} give you a new scoped block). Try this:
while (i < 2) {
funcArray[i] = (function(value) {
return function(){ alert(value); return false; }
})(i);
i++;
}
Curly braces ({}) in JavaScript do not capture variables as they do in C#.
Only closures (functions) introduce new scope, and capture variables.
var i = 0;
while (i < 2) {
var contextCopy = i;
...
}
is actually interpreted as:
var i, contextCopy;
i = 0;
while (i < 2) {
contextCopy = i;
...
}
To get a copy of the variable, you'll need to wrap the code with a closure:
var i;
i = 0;
while (i < 2) {
(function (contextCopy) {
...
}(i));
}
You don't create a copy of the i variable. Instead, you make this variable GC-dependant of the closures that use it. It means that when the while loop exits, the i variable continues to live in its last state (1) and both closures reference to it.
Another way to put it: closing over a variable does not copy it into your closure (would make little sense for objects), it just makes your closure reference the variable and ensures this variable is not GCed untill the closure is.
I have some javascript code that resembles this:
for (i = 0; i < numTimes; i++) {
DoStuff();
}
function DoStuff() {
for (i = 0; i < 100; i++) {
console.log(i);
}
}
I am finding that the second time the DoStuff() is called, the value of i in the loop starts with 1. I assume this is due to the way the scoping of variables work in JS. Other than changing the variable name in the DoStuff() function, what's the cleanest way of resolving this and can someone explain this behavior?
EDIT: Thanks for the responses. It appears that JS has "lexical scope" instead of "block scope". Is this what I am seeing here? Can someone explain what lexical scope is in newbie terms?
for (var i = 0; i < numTimes; i++) {
DoStuff();
}
function DoStuff() {
for (var i = 0; i < 100; i++) {
console.log(i);
}
}
In javascript, any variable that isn't first declared with the var keyword is global. Adding the var keyword makes it function-local, that is local to the function. Javascript doesn't have block scoping, so if for instance you declared a variable with var inside and if block, it would not be local to the if block, it would be local to the function that contains it.
Use the var keyword. This will limit the scope of i
for (var i = 0; i < 100; i++)
Put a var in front of the variable inside the for loop:
for (var i = 0; i < 3; i++) {
console.log(i);
}
for (var i = 0; i < numTimes; i++) {
DoStuff();
}
function DoStuff() {
for (var i = 0; i < 100; i++) {
console.log(i);
}
}
You should declare iterator variable with "var". If you do not, then you declare global scope variable
your i variable is being implicitly set at a global level, meaning it is accessible (and modifiable!) to any script, anywhere. This is a bad thing, as you have just discovered. The solution is to use the var keyword, which limits the variable to the nearest enclosing function:
for(var i=0; i<100; i++){
To elaborate on a previous answer,
changing i = 0 to var i = 0 will give you the behavior you're looking for.
The reason for this is that if you declare without the var you are declaring it as a global variable, whereas declaring with var makes it local within the scope of the function it is defined in. It should be also be noted that variables declared outside a function, with or without var will be global.
More info here
If you don't declare your variable (i.e. using "var i;" or "var i=0;"), it is created as a global variable, and its scope is the whole program. (THIS IS VERY BAD!).
Note also that JavaScript does not have block scope, and so if you declare your variable in the for loop, it still has scope for the entire function.
Change i = 0; to var i = 0; Example:
for (var i = 0; i < numTimes; i++) {
DoStuff();
}
function DoStuff() {
for (var i = 0; i < 100; i++) {
console.log(i);
}
}
for (var i in variables) {
eval('var ' + i + ' = variables[i]');
}
Basically, I want to transfer variables properties to local variables.
Is there an alternative to using eval()
Or which of these is better:
1.
var _ = variables;
for (var i = 0; i < 100000; i++) {
_.test1();
_.test2();
_.test3();
}
2.
with (variables) {
for (var i = 0; i < 100000; i++) {
test1();
test2();
test3();
}
}
3.
var test1 = variables.test1,
test2 = variables.test2,
test3 = variables.test3;
for (var i = 0; i < 100000; i++) {
test1();
test2();
test3();
}
4.
for (var i in variables) eval('var ' + i + ' = variables[i]');
for (var i = 0; i < 100000; i++) {
test1();
test2();
test3();
}
By looking at your comment seems that your major concern is having to reference several times a deeply nested object, avoiding eval and with I would simply recommend you to use an alias identifier, for example:
// some local scope...
var foo = namespace.constructors.blah.dramatic.variables;
// replace foo with something meaningful :)
foo.method1();
foo.method2();
foo.property1;
// etc...
In that way the deeply nested object will already be resolved and referencing your alias will be faster, eval and with IMO would only cause you more problems than benefits in this case.
One alternative is to make the object the current scope. It won't make the properties local variables, but you can access them using the this keyword:
var variables = { a:42, b:1337 };
(function(){
alert(this.a);
alert(this.b);
}).apply(variables);
This has the advantage that you are not copying anything anywhere, you are accessing the properties directly.
Well, I'll post the answer myself.
No.
There is no way to set local variables without knowing the name of the variable without using eval()...
...But using local variables (option 3) is the best way.
The following code in VS2008 gives me a "variable is already defined" warning:
if (someVar) {
var a = 1;
}
else {
var a = 2;
}
The warning is given on the second var a = .... To cure this warning I have done:
var a;
if (someVar) {
a = 1;
}
else {
a = 2;
}
But is this the correct way to do it?
Thanks,
AJ
Yes, that is the correct way to do it. There is no block scope in javascript; there is only function scope and global scope.
you could also give each "block" functional scope using anonymous functions, although it's not very practical in this case:
if (someVar) {
(function () {
var a = 1;
})();
}
else {
(function () {
var a = 2;
})();
}
As a side note, this is also why for (var i = 0; ...) is discouraged in favor of var i; for (i = 0; ...), to avoid 2 consecutive loops in the same function both trying to declare the variable i
It depends on how you're using those variables afterwards.
If they relate to the same object, then it's the correct way.
If they relate to different objects, then I'd rename the variables, as this would prevent maintenance issues in the future.
Either way is perfectly valid and fine (both examples ensure that the variable is declared with the var keyword), but generally it's best practice to declare your vars at the top of the current code block, as in your second example.