Since I am a JavaScript newbie, I started learning it but I got stuck just at the beginning.
I am following a Mozilla Tutorial and I have a problem with variable scope in JavaScript. I have some code:
var myvar = "my value";
var zmienna = "string";
(function () {
alert(myvar);
alert(zmienna);
})();
(function () {
alert(myvar); // undefined
var myvar = "local value";
alert(zmienna);
})();
In the tutorial, I've read that JavaScript variables are not visible from function blocks. Well, first two alerts says correct values. It's strange then, because third alert says "undefined", despite fact that nothing has changed from previous function block. The fourth one, again, prints right value.
Could anybody explain me, what is happening here? I would be very glad, because tutorial says nothing more about that.
The use of var is hoisted.
Since you have var myvar inside the function, there is a locally scoped myvar. Since you assign a value to it after you alert it, it is undefined when you alert it.
"I've read that JavaScript variables are not visible from function blocks."
That's not quite right. They are available from the nested functions.
Nested functions create a scope chain. A function created inside another function has access to its own variables as well as the variables of the function in which it was nested.
But function A can not see the variables of function B if function A was not nested inside function B.
var myvar = "my value"; // <-- global variable, seen by all functions
var zmienna = "string"; // <-- global variable, seen by all functions
(function () {
alert(myvar); // <-- referencing the global variable
alert(zmienna); // <-- referencing the global variable
})();
(function () {
// v--- Declaration of these "local" variables were hoisted to the top...
// var myvar; // <--- ...as though it was here.
// var new_var; // <--- ...as though it was here.
alert(myvar); // undefined (myvar is delcared, but not initialized)
alert(new_var); // undefined (new_var is delcared, but not initialized)
var myvar = "local value"; // <-- assign the value
alert(zmienna); // <-- referencing the global variable
alert(myvar); // <-- referencing the local variable
var new_var = "test"; // <-- another new local variable
// A nested function. It has access to the variables in its scope chain.
(function() {
alert(myvar); // <-- referencing the variable from its parent func
alert(new_var); // <-- referencing the variable from its parent func
})();
})();
/*
Here's a new function. It was not nested inside the previous function, so it
has access to the global variables, and not the locals of the previous func
*/
(function () {
alert(myvar); // <-- referencing the global variable
alert(new_var); // <-- ReferenceError
})();
Related
I initialized a variable of function through "." outside the function. Regarding closure rule, it should be set inside the function scope and after execution should be gone. But
Why after calling the function variable still exists?
Why can I access inside a function only through "."?
I initialized variable outside of the function through "." like f1.a = "any variable".
I checked if the variable of function initialized outside is accessible inside a function without ".":
I tried to get access to the variable inside the function. It seems if I get access to the variable by itself without "." it gives me an error "variable is not defined".
I checked if the variable of function initialized outside will be gone after function execution:
I call function and check if the value of the variable after execution is still available. Yes, it was still there.
f1.a = "any variable";
function f1(){
(function()
{
console.log(a);
}()) // a is not defined
}
f1();
console.log(f1.a); // after f1(), f1.a still exist
I expected variable "a" visible by itself inside the "f1" since I initialized inside the function scope f1.a = "any variable", but I can get access only with "."
I expected variable "a" will be gone after execution f1(), but it still exists
There are several things you need to understand to get a clear idea of what's happening here. First, JavaScript hoists function definitions to the top of the file.
Knowing that, you could imagine your code is something like this once JavaScript interprets it:
var f1 = function (){
(function()
{
console.log(a);
}()) // a is not defined
}
f1.a = "any variable"
f1();
console.log(f1.a);
Secondly, in your first console.log(a) you are referencing a variable a which was never declared. If you change that to console.log(f1.a) you'll see the value of f1.a as expected.
It's also not clear why you are using an immediately invoked function inside of your f1 function. It makes analyzing this code even more complex. It seems like you are trying to get a better understanding of how closures work? But for closures you should be interested in variables declared inside of f1, rather than properties of f1. For example, something like this.
f1 = function (){
var a = 'something'
return function()
{
console.log(a);
}
}
var closure = f1();
// f1 is finished running here.
closure(); // closure still has access to f1's variable.
I think the three areas you could learn more about to understand the code above are 1. Scope and especially hoisting 2. Objects, this and object properties and 3. Closures.
JavaScript normally follows the function scope i.e. variables are accessible only within the function in which they are declared.
One of the ways to break this convention and make the variable accessible outside the function scope is to use the global window object
e.g.
window.myVar = 123;
My question is are there any other ways in JavaScript/jQuery to make the variable accessible outside the function scope?
Not with variable declarations, no. You can obviously declare a variable in an outer scope so that it's accessible to all descendant scopes:
var a; // Available globally
function example() {
a = "hello"; // References a in outer scope
}
If you're not in strict mode you can simply remove the var keyword. This is equivalent to your example:
// a has not been declared in an ancestor scope
function example() {
a = "hello"; // a is now a property of the global object
}
But this is very bad practice. It will throw a reference error if the function runs in strict mode:
function example() {
"use strict";
a = "hello"; // ReferenceError: a is not defined
}
As you wrote, variables are only visible inside the function in which they were defined (unless they are global).
What you can do is assign variables to the function object itself:
function foo() {
foo.myVar = 42;
}
console.log(foo.myVar); // outputs "undefined"
foo();
console.log(foo.myVar); // outputs "42"
But I would advise against it. You should really have a very good reason to do something like this.
You can define them as part of a global object, then add variables later
// Define your global object
var myObj = {};
// Add property (variable) to it
myObj.myVar = 'Hello world';
// Add method to it
myObj.myFunctions = function() {
// Do cool stuff
};
See the link below:
Variable declaration
Also, you can declare it without the var keyword. However, this may not be a good practice as it pollutes the global namespace. (Make sure strict mode is not on).
Edit:
I didn't see the other comments before posting. #JamesAllardice answer is also good.
My tests suggest that the title is, indeed, correct. But I don't know if there is some subtle nuance that I'm not thinking of. See also: Is there anything wrong with declaring your vars inside of a for loop or an if block?
If they are in the same scope, your test is right. Redeclaring the same variable in the same scope does nothing.
But, if they are not in the same scope, re-declaring a variable in a local scope will create a new variable that will override the original within that scope.
So, this works fine:
var value = "foo";
var value;
console.log(value); // "foo"
But, this creates a new variable in the local scope that does not have the value of the globally defined one:
var value = "foo";
function test() {
var value; // this creates a new variable that is separate
// from the globally declared one with the same name
console.log(value); // undefined
}
test();
I'm trying to understand why this alerts to true? And how I would be able to alert false without passing arguments to the callback function (if possible)?
var a = true;
function foo(callback){
var a = false;
callback();
}
function bar(){
alert(a);
}
foo(bar); // Alerts true
Since you use var when you say a = false you create a new, locally scoped, a.
Get rid of the var if you want to modify the existing variable in the wider scope.
This:
var a = false;
...is local to the scope of the foo function.
This:
function bar(){
alert(a);
}
...was created in the variable scope where a = true, and as such, closed over that local variable environment, and thus over that specific a variable.
It comes down to the fact that whenever you create a function, it permanently retains the variable scope in which it was created.
It doesn't matter if you pass that function into another environment. It will always only reference its original variable environment.
var test0 = 0; // global variable environment
function a() {
var test1 = 1; // "a()" will always retain the global environment even if you
// send "a()" somewhere else
function b() {
var test2 = 2; // "b()" will always retain the environment of "a()" and the
// global environment, even if you send "b()" somewhere else
}
}
EDIT:
In order for bar to reference the variables local to foo, you could pass them in to bar as arguments:
var a = true;
function foo(callback){
var a = false;
callback( a );
}
function bar( a ){
alert( a );
}
foo(bar); // now it alerts false
Example: http://jsfiddle.net/dSZ4M/
...you'll notice that I gave the parameter in bar() the same name as the global a variable. Because parameters to the function are read before variables outside the function's own variable environment, the a parameter "shadows" the global a variable.
As such, you can no longer read the global a from inside bar. Of course, all you'd need to do is change the name of the parameter to something else, like arg or whatever, and then you'd be able to reference both the local arg parameter and the global a variable.
If a variable could be defined in a function, even if no value is assigned, it becomes a local variable
so, is testB() better programming?
var test = 'SNAP!'
function testA(boolean) {
if (boolean) var test = 'OK';
else var test = null;
alert(test);
}
function testB(boolean) {
if (boolean) var test = 'OK';
alert(test);
}
testA(true); // 'OK'
testB(true); // 'OK'
testA(false); // null
testB(false); // undefined, no error
In my specific case test's global value ('SNAP!') is neither expected nor required.
You can't declare variables conditionally.
Why?
The variable instantiation process occurs before the actual code execution, at the time the function is executed, those variables will be already bound to the local scope, for example:
function foo () {
if (false) {
var test = 'foo'; // never executed
}
return test;
}
foo(); // undefined
When the function is about to be executed, identifiers of formal parameters, identifiers from variable declarations, and identifiers from function declarations within the function's body are bound to the local variable environment.
Variables are initialized with undefined.
Also, identifiers in the local scope shadow the others with the same name, higher in the scope chain, for example:
var test = 'global';
function bar () {
alert(test); // undefined, not "global", the local variable already declared
var test = 'xxx';
}
bar();
If the test variable were not declared anywhere, a ReferenceError will be thrown:
function foo () {
return test;
}
try {
foo(); // ReferenceError!!
} catch (e) {
alert(e);
}
That's one of the reasons about why for example, JSLint recommends only one var statement at the top of functions, because for example, the first snippet, will actually resemble this when executed:
function foo () {
var test; // var statement was "hoisted"
if (false) {
test = 'foo'; // never executed
}
return test;
}
foo(); // undefined
Another reason is because blocks don't introduce a new lexical scope, only functions do it, so having a var statement within a look might make you think that the life of the variable is constrained to that block only, but that's not the case.
Nested function declarations will have a similar behavior of hoisting, they will be declared before the code execution, but they are initialized in that moment also:
function foo () {
return typeof bar;
// unreachable code:
function bar() {
//..
}
}
foo(); // "function"
If the variable does not need to be manipulated by any other functions, keep the variable inside a function with var foo;.
Otherwise, if it does need to be accessed and read in multiple scopes, keep it outside. But remember that when you keep it outside, it becomes global. That is, unless you wrap everything in a self executing function, which is the best way:
(function() {
var president='bush';
function blah() {
president='reagan';
}
function meh() {
president= 'carter';
}
document.getElementById('reagan').onclick=blah;
document.getElementById('carter').onclick=meh;
})();
alert( president ) // undefined
The above is perfect for a variable accessed by functions defined inside of that scope. Since there are 2 elements i click to set the president, it makes sense to define it outside both functions because they set the same variable.
So, If you are not dealing with multiple functions changing the exact same variable, keep them local to the function.
Is testB better programming? No, because it gives an unexpected result of "undefined" (at least, I was surprised by that) and it is hard to read.
Generally, variables should be limited to the scope that requires them, so if the "test" variable is not needed outside the function it should be declared local. To avoid confusion, declare your variable before using it:
function testC(boolean) {
var test;
if (boolean) {
test = "OK";
}
else {
test = null;
}
alert(test);
}
Unless you genuinely want to change the global scope version of "test", in which case don't use the var keyword inside the function.
If you ever find yourself using the same name for a local variable and a global variable you might consider renaming one of them.