JavaScript Scope

This jumps right into it so you should make sure that you understand general scope first before jumping into this.

Declaring of Variables

There are generally 2.5 ways of declaring a variable. Technically only 2 but there is an outlier that seems to break the rules so we will cover it.

Version 1: Global Variable

global_var = 'This is global.';

You will notice it is not prefixed with the var keyword. When you don't specify var it uses the variable by that name that is in scope. If there is no variable in scope it puts it at the very highest level and it becomes a global variable. This variable and its contents are available absolutely everhwere unless an identically named variable is added to the scope that temporarily overrides it.

Version 2: Scoped Variable

var scoped_var = 'This is scoped.';

Notice that this starts with the var keyword. This means that this variable is available to anything within this scope or any nested code blocks but it is not available everywhere like the global variable is.

Version 2.5: Assigning a Value To An Object Attribute

var object = { };
object.cow = 'Moo'

This one doesn't use the var keyword to set the 'cow' variable to 'moo'. This is because it is setting an attribute on an object. The object itself is already in its own scope so it wouldn't make sense for me to use var as the scope is already well established.

Scope Hierarchy

You can have variables with the same name. The one you access is the one that is closest to you in the scope.

Here is an example of how that works and also a good example of absolutely terrible programming.

example_var = 'Global Variable';
// Right here example_var equals 'Global Variable'

var example_var = 'Scoped Variable';
// Now example_var exists both in the global scope and the
// local scope.  If you accessed it now it would equal
// 'Scoped Variable'.  If you accessed example_var from
// another file or elsewhere it would still equal 'Global Variable'

function() {
    // Now the closest scope to us is the 'Scoped Variable' so
    // that is what our example_var is set to right now.

    // Let's get tricky!
    example_var = 'Still In The Same Scope';
    // Notice that we didn't specify 'var'.  The compiler walks
    // backwards through your code refs looking for the closest
    // scope that contains the 'example_var' variable.
    // In this case, it found the 'Scoped Variable' version and
    // Changed the value to 'Still In The Same Scope'.

    // Let's do some horrible programming.
    var example_var = 'Now we are in different scope.';

    // Technically there are now three different example_var(s)
    // in memory.  From closest to farthest away.
    // The Third Scope:  'Now we are in a differnt scope.'
    // The Second Scope: 'Still In The Same Scope'
    // The Global:       'Global Variable'

    function() {
        // Again assigning without the 'var' we are accessing the
        // closest scope.
        example_var = 'Still in a different scope.';

        // New scope.  Now we have 4 versions of example_var.
        var example_var = 'This is getting stupid.';

        // From closest to farthest away.
        // The Fourth Scope: 'This is getting stupid.'
        // The Third Scope:  'Still in a different scope.'
        // The Second Scope: 'Still In The Same Scope'
        // The Global:       'Global Variable'
    }
    // As we leave this scope the fourth scope ceases to exist.
    // We once again have 3 scopes.  You will notice, however
    // that the change to the one that is still in our scope
    // remains changed.

    // From closest to farthest away.
    // The Third Scope:  'Still in a different scope.'
    // The Second Scope: 'Still In The Same Scope'
    // The Global:       'Global Variable'
}
// As we leave this scope the third scope also ceases to exist.
// We now have only 2 scopes left.

// From closest to farthest away.
// The Second Scope: 'Still In The Same Scope'
// The Global:       'Global Variable'

// There are some fancy ways to still access the 'global' version
// of the variable but that is a lesson at another time.

The Invisible Scope

There is one place that there is a scope as if it is a code block but it is not entirely obvious that it is its own code block.

Each file has its own scope. Here is an example of two files. What is available between them and what is not.

// File #1
global_from_file_1     = 'I exist everywhere.';
var scoped_from_file_1 = 'I only exist in file 1.';

// Right here in this file we have access to:
// global_from_file_1 = 'I exist everywhere.';
// global_from_file_2 = 'I alsoexist everywhere.';
// scoped_from_file_1 = 'I only exist in file 1.'.
// File #2
global_from_file_2     = 'I also exist everywhere.';
var scoped_from_file_2 = 'I only exist in file 2.';

// Right here in this file we have access to:
// global_from_file_1 = 'I exist everywhere.';
// global_from_file_2 = 'I alsoexist everywhere.';
// scoped_from_file_2 = 'I only exist in file 2.'.

Pitfalls of Scope

This is one that will get you every time and I'll use a recent example of a mistake I made.

var obj_constructor_1 = function() {
    self = this;
    self.identity = 'This is object 1';

    self.output_identity = function() {
        console.log(self.identity);
    };
};

var obj_constructor_2 = function() {
    self = this;
    self.identity = 'This is object 2';

    self.output_identity = function() {
        console.log(self.identity);
    };
};

var obj_1_instance = new obj_constructor_1();
var obj_2_instance = new obj_constructor_2();

obj_1_instance.output_identity();
// Output: 'This is object 2';
// Wait what?!  Why!!
obj_2_instance.output_identity();
// Output: 'This is object 2';

// We did not scope 'self' and instead put it in the global scope.
// So object 1 instantiates and sets global 'self' to 'this'.
// 'this' is a special variable I should totally write a page about.
// In short, 'this' should refer to obj_1_instance.

// Next we instantiate object 2 which overwrites the value of the global
// 'self' variable to it's version of 'this'.

// Now we run 'obj_1_instance.output_identity()' which runs the
// following function:

// function() {
//     console.log(self.identity);
// };

// It outputs what is in 'self.identity'.
// The problem is it works its way backwards through the scope
// to find which is the nearest scoped version of 'self' and this
// happens to be the global 'self' which is now set to obj_2_instance.
// Not surprisingly we get obj_2_instance's identity.

// Just to further prove that 'self' is in the global scope we can do:

console.log(self.identity);
// Output: 'This is object 2';

// This was definitely not our intent.

This is a classic scoping mistake and it can be so hard to diagnose becuase nothing will make sense and you will become convinced that the devil has taken up residence in your code.

Simply scoping 'self' will make this behave properly.

var obj_constructor_1 = function() {
    var self = this;
    self.identity = 'This is object 1';

    self.output_identity = function() {
        console.log(self.identity);
    };
};

var obj_constructor_2 = function() {
    var self = this;
    self.identity = 'This is object 2';

    self.output_identity = function() {
        console.log(self.identity);
    };
};

var obj_1_instance = new obj_constructor_1();
var obj_2_instance = new obj_constructor_2();

obj_1_instance.output_identity();
// Output: 'This is object 1';
obj_2_instance.output_identity();
// Output: 'This is object 2';

console.log(self.identity)
// Some sort of 'Uncaught ReferenceError: self is not defined' error.

// Why is this different?

// This is actually a very basic example of a closure. So you are
// getting two knowledge points for the price of one.

// This next sentence explains closures so read it multiple times.:

// Variables in scope as of the time of creating a function are
// still in scope when that function is called later regardless
// of the scope in which the function is finally called in.

// To be clear the 'Variables in scope' means the pointers to where
// the value is stored in memory but not the value itself.  If the
// value changed multiple times in the meantime you will get whatever
// the latest value is, not the value it was at time of defining the
// function.

// 'self' is inside the scope of the constructor.  The
// 'output_identity' function is declared in this same scope.

// As in the previous example we run 'obj_1_instance.output_identity()'
// which runs the following function:

// function() {
//     console.log(self.identity);
// };

// The difference is that when this function was created there was a varible
// called 'self' in scope and that variable was pointed to the 'this' of
// the obj_1_instance.  So now when we look at 'self' it is the self that
// was inside the constructor and not a global 'self.

Avoid The Global Scope At All Costs

You do not want to save anything to the global namespace unless you absolutely have to.

You should leave that space for the browser because god knows what values the browser uses temporarily in the global name space. Imagine you used something like 'page'. And the browser uses that temporarily as a way to hold some information about the current page you are on. Now you've lost your value for forever and have some unexpected junk in there.

Instead you should throw stuff into a namespace. Namespace and scope are frequently tied together but do not mean the same thing.

On this site I have stored almost everything into an object called ZolanSite in the global scope. All of my specific information is stored inside it. For example, the configuration is inside of ZolanSite. Everything that controls my website are all located in ZolanSite. I'm literally taking up one single thing in the global namespace (which is in the global scope) and there is little chance that anything else will ever unexpectedly access that. ZolanSite would be considered a namespace. A namespace is the idea of consolidating similar data or code under one 'name' for organization and to avoid polluting the global namespace with data everywhere.

Everything JavaScript related to my website (excluding external libraries which are in their own namespaces) are all in ZolanSite.

Summary

Hopefully that gives you a better understanding of scope and the pitfalls you can run into using the global scope.

This is specifically JavaScript scope but the majority of this information carries over into other languages. Not all languages have closures and not all languages have file-based scope but the rest is fairly universal.

Best of luck out there. Hopefully this article has helped someone.