user1512966
user1512966

Reputation: 57

variable scope clarification

I have a snippet of javascript code, that I have found in the "Secrets of the JavaScript Ninja" (a John Resig book). I have a problem to understand the behaviour of a variable. The following is the code (simplified with respect to the original one):

(function() {
    var results;
    this.assert = function assert() {
        var li = document.createElement("li");
        results.appendChild(li);
        return li;
    };
    this.test = function test(name, fn) {
        results = document.getElementById("results");
        results = assert().appendChild(document.createElement("ul"));
        fn();
    };
})();
window.onload = function() {
    test("A test.", function() {
        assert();
        assert();
    });
};

My problem is the results variable. When you enter the "test" function, the results variable will take the value "ul#results" first and then the value "ul", as a result of the appendChild function. But when you enter the "fn()" function the value of "results" is still "ul#results". Why? I have some difficult to understand the scope of this variable.

Can someone help me to understand this topic?

Thank you very much.

Upvotes: 2

Views: 85

Answers (2)

Lucavai
Lucavai

Reputation: 101

This may be more confusing than anything but basically the code is recursively adding some child element to an element in the DOM. This is made possible because of the variable "results" defined in the closure. That variable remains alive within that scope.

BTW, here is an article that shows some tests that explain variable scope in javascript. Keep in mind that it does NOT talk about closures, like you have here. But it may help explain some other things you'll run into.

http://www.computerhowtoguy.com/an-introduction-to-javascript-variable-scope/

This is the process explained step by step:

  1. The code in the window.onload function will have "window" level scope. If you were to output "this" you would get the "window" object.
  2. The onload function calls "test". "test", which could also be written this.test or window.test, is in a closure. The reason it's the "test" function in the closure is because of this line: this.test = ... Since the closure was executed at the window level, "this" refers to the "window" object.
  3. In the "test" function call "this" refers to the "window" object. Since the assert function above was defined with: this.assert = ... (window.assert) it is at the window object level. If you did assert == window.assert you would get "true", because both are just two names for the same (function) object.
  4. This line is run: assert().appendChild(document.createElement("ul")) The assert function is executed before anything after it. So let's go into the assert code...
  5. In assert "this" is still the "window" object. A local variable "li" is created, referencing a new DOM element that has not been attached to the document element yet. We then append this new li element to the "results" DOM element. Now it's part of the document DOM object. A javascript reference to the li element is returned.
  6. Now back to: assert().appendChild(document.createElement("ul")) The appendChild function is called, appending a new 'ul' element to the newly created 'li' element. Next, our javascript "results" object is once again reassigned, this time to the newly created ul element. Finally, 'fn' (our anonymous function from way back) is called.
  7. Into the "fn"... "this" still refers to window. assert is called, assert still being a reference to the window.assert function A new li element is created and appended to the "results" variable. Rememeber, the last thing "results" was assigned to was the ul element, so we are appending a new li to it.

The DOM structure would at this point look something like this:

<div id="results">
    <li>
        <ul>
            <li></li>
        </ul>
    </li>
</div>

And so the code moves on with the same sort of thing...

This is the code revised, now with comments:

// the following is a closure.  a sort of isolated container with its own scope.
(function() {
    // results is not globally scoped, only scoped at the closure level,
    // since its defined with "var".
    var results;

    // "this" is the calling object. (ie: window object)
    this.assert = function assert() {

        // since "var" is used "li" is part of this function.
        var li = document.createElement("li");

        // results (at the closure level) appends a child at this function's level.
        results.appendChild(li);

        // return a javascript reference to the new DOM element.
        return li;
    };

    // again "this" is the calling object.  when called in onload below, "this" is the window object.
    this.test = function test(name, fn) {

        // results refers to the closure level results variable, since var is ommitted.
        // this is a reference to an element in the DOM.
        results = document.getElementById("results");

        // changing the variable now. the DOM object "results" is NOT altered by this assignment, since
        // javascript is separate from the DOM.
        // NOTE: the assert function was previously assigned to the window object previously.  so stuff in that
        // function will be window scoped.
        results = assert().appendChild(document.createElement("ul"));

        // call fn
        fn();
    };
})();

window.onload = function() {

    // at this point, "this" is the "window" object.
    // "test" is part of the closure above.  in the closure the test function is assigned
    // to "this".  since we are calling the function here, "this" will be the window object in the
    // closure for this call.
    test("A test.", 
        // an anonymous function.  this is really just an object passed into the "test" function
        function() {
            assert();
            assert();
        }
    );
};

Upvotes: 0

ThiefMaster
ThiefMaster

Reputation: 318488

The variable is created in the scope of the anonymous function. Both assert and test access the same results variable.

Upvotes: 2

Related Questions