Why does a child object in JavaScript lose the global scope?
I am striving to follow Douglas Crockford's advice from "JavaScript: The Good Parts" and his web site:
Use of global variables should be minimized. Implied global variables should never be used.
To do this, I have defined one "root" object that acts as a container for all other objects, and everything is now arranged into a logical hierarchy of shared functionality.
Where I am stumped is how child objects seem to lose the global object's scope. The best example I can think of here is my logger, which I want to define globally as root.log and reuse it everywhere else.
However, when I try to access root.log inside a child object my code fails because it can't see any reference to the root object any more. I move the child object out into the global scope and it sees everything fine again.
I have seen other posts on Stack Overflow that provide solutions for parent/child object communication by explicitly passing the parent reference forward into the child, but that's not really what I'm after here. I want to be able to access the root from any point and if I'm three or four levels down I don't want to have to deal with tracing back up the chain.
An explicit example of that might be if I'm deep down in my utility hierarchy and I want to log a message. Let's say I'm at root.util.array.getPositionInArray() and I've passed the parent value into each sub-object. I don't want to have to call parent.parent.parent.log.write , I just want to make one simple call to root.log.write .
I could pass the root and parent object references into each subobject as they are created, or maybe experiment with some inheritance principles and see if I can get it to work that way.
My questions are as follows:
Why does the global scope "disappear" when I'm in an object defined inside another object?
Is there a simple way to gain access to that global variable from inside subobjects?
(Perhaps a duplicate of 2) What is the recommended way to handle this?
My sample code is below (here it is loaded into jsfiddle)
// declare root object as global variable
var root = null;
$(document).ready(function() {
// instantiate root
root = new Foo();
// uncomment to instantiate child separately
// child = new ChildFoo();
// write to log from outside parent (shows scope is global)
root.log.write(root.x)
root.log.write(root.child.x);
});
function Foo() {
// instantiate logger as child of parent
this.log = new Logger("output");
// write a quick message
this.log.write("Foo constructor");
// set value of x
this.x = 1;
// instantiate child object
this.child = new ChildFoo;
}
// child object definition
function ChildFoo() {
// why is root.log == null here?
root.log.write("Child constructor");
// this reference to parent also fails
// this.x = 10 * root.x;
this.x = 10;
}
// log object definition
function Logger(container) {
// store reference to dom container
this.container = container;
}
// method to write message to dom
Logger.prototype.write = function(message) {
$("#" + this.container).append("[" + new Date() + "] " + message + "<br>");
}
I have been able to get this to work by adding the following section to the top of the Foo object definition. This immediately provides the global object reference to the root object, and also implements a Singleton pattern to make sure that there is only ever one root object. The jsfiddle has been fully updated with this.
if(root != null){
root.log.write("Root object already instantiated");
return root;
} else {
root = this;
}
The problem is that you're calling...
var parent = null;
$(document).ready(function() {
parent = new Foo();
// ...
});
...which calls Foo
...
this.log = new Logger("output");
this.log.write("Foo constructor");
this.x = 1;
this.child = new ChildFoo;
...which calls ChildFoo
, which tries to access parent
...
parent.log.write("Child constructor");
This is all in one invocation, so the original new Foo
has not yet completed before you try to access parent
, so parent
is still null
.