On 21/06/2011 19:48, DrKen wrote :
Hi
When you have a new question, I suggest that you open a new thread
rather than continuing in a previous thread. Readers only read new posts
in new threads or in threads they are watching, so you will be sure to
maximize your number of readers this way.
Thanks everyone for the detailed, helpful responses. Just to be sure
I understand how it works, if one JavaScript piece creates a var, is
that available to other JavaScript blocks on the same page? Or, do I
need to do something else to make the value I'm saving a part of the
document? That is, do all JavaScript bits in a page see all the same
vars and so forth, or only the piece that creates a var? Thanks.
Evertjan has already provided answers to this, but let's detail a bit
how the "var" keyword works. The approach I have chosen might surprise
you a bit, but if at the end you understand all of it, then you will be
a nice step further in your understanding of javascript.
Firstly, I would like you to think a few minutes minutes about the
following: if you were to implement an interpreter for javascript code,
how would you proceed? For instance, suppose that you are a browser,
that you have started parsing an HTML page, that you meet a SCRIPT tag
which content is javascript, that you grab this content as a big string,
and that you want to "understand" and "execute" it.
Soon enough, you will see that you need to analyze the big string
(recognizing tokens), see if it is valid javascript (applying grammar
rules), and it if is, create some objects (in Java, C, or whatever
language you proud browser are made of) that represent the javascript
code, and can be used to run the script.
For instance, if you have the text...
---
function foo (bar) {
return false;
}
---
.... then you must recognize:
- the "function" keyword,
- the opening and closing parentheses for the function's arguments,
- the identifiers "foo" and "bar",
- the opening and closing parentheses for the function's body,
- the "return" keyword,
- the "false" keyword,
- the ";" token, which marks the end of a statement,
- the line breaks at the end of each line.
Once you have done that, you must check that this piece of code is valid:
- you must recognize a valid FunctionDeclaration,
- you must recognize a valid Statement (the "return false" thing).
Yes, browsers have it tough! And I' not even talking about HTML tag soup
processing... Anyway, so far, so good, that thing looks healthy. Now you
can build your internal representation of the script, for instance
(using a Java-like language):
---
Function f = new Function();
f.setIdentifier("foo");
f.addArgument(new FunctionArgument("bar"));
f.addStatement(new ReturnStatement(FALSE));
---
Still with me? Then let's stay under the hood for a while. Since we have
to keep these variables somewhere, we keep them on an object we call
"Variable" (a sort of Map with keys being the variable identifier, and
values being the variable value). This Variable Object is kept on an
object we call "Context".
---
Context context= new Context();
Variable variable= context.getVariable();
variable.put(f.getIdentifier(), f); // register the function
---
The javascript specification tells use when we should create context
objects:
- before the script is analyzed, we create a default context, which we
call the global context,
- for each function in the script, we create a dedicated context, in
which the function code is to be run,
- for each "eval" code in the script, we create a dedicated context, but
we won't dwell on that.
So, what does the "var" statement? It's pretty straightforward: it is
simply a way to put a new entry in the variable object of the current
context.
For instance, if you have the following...
---
var a="hello, global!";
function foo() {
var a="hello, foo!";
function bar() {
var a="hello, bar";
}
}
---
.... then you must create three contexts, each having its own variable
object, and you must assign, on each variable object, an entry whose key
is "a" and value is "hello, global!", "hello, foo!" or "hello, bar!",
depending on the current context.
Is that it? Pretty much, but two things:
- a "var" statement may or may not assign a value to the variable. You
can have many "var" statements for the same variable identifier in the
current context, but that's pretty useless, as the property already
exists in the current Variable Object. However, values for the variables
still would be assigned in such cases.
- javascript may declare variables for you. For instance, function
declarations (as we have seen previously), are automatically set on the
parent context Variable Object, and function arguments are automatically
set on the function Variable Object.
Well, you have successfully declared your variables, congratulations!
Now, declaring variables is easy, but reading them is a bit more tricky.
Let's discuss this a bit. Suppose you have the following statement:
---
alert(a);
---
How to interpret this? It's easy to recognize that "alert" is a function
object, that "a" is an identifier. But how do we find the value of "a"?
If "a" equal to "hello, global!", "hello, foo!" or "hello,bar!"?
It all depends on where the statement occurs. Let's work with the
following hypothesis: to find the value of the identifier, you grab the
current context, from which you fetch the variable object, from which
you read the value. So if you're in a function, you grab the function's
context, get its variable object, and read the value from it. Looks
simple, doesn't it?
Wait, but what about the following:
---
var a="global";
function foo() {
alert(a);
}
foo();
---
When we execute the function, it will alert "global", but according to
our hypothesis, it should not, right? Indeed, the identifier "a" is
registered on the Variable Object of the global context, not on the
Variable Object of the function context! This means that javascript,
somehow, provides some sort of chaining mechanism: if the variable is
not found in the current context, then it searches in the "enclosing"
context.
This chaining mechanism is implemented with a new kind of object, which
we call "Scope". The Scope object is a stack of objects, which must be
searched successively to resolve an identifier. Once the identifier is
resolved, then you may read or write its value.
So when we create the global context, we actually do the following:
---
Context globalContext = new Context();
Variable globalVariable = new Variable();
Scope globalScope = new Scope();
globalScope.push(globalVariable); // The magic is here!
globalContext.setVariable(globalVariable);
globalContext.setScope(globalScope);
---
As you can see, the Variable Object is set in two places:
- directly on the context, in order to set variables,
- on the scope attached to the context, in order to read/write
variables' values.
So, getting back to our last example, what happens with the function? It
has its own context, so its own Variable object and its own Scope
object. What happens with the scope object, however, is particular: the
interpreter adds the Variable object on top of it, but it also adds all
the objects contained in the scope of the enclosing context!
---
// global stuff
Context globalContext = getGlobalContext();
Variable globalVariable = globalContext.getVariable();
Scope globalScope = globalContext.getScope();
// function stuff
Function function = new Function();
Context functionContext = new Context();
Scope functionScope = new Scope();
Variable functionVariable = new Variable();
// assemble function scope
functionScope.add(functionVariable);
functionScope.addAll(globalScope); // The magic. Again.
// assemble function context
functionContext.setScope(functionScope);
functionContext.setVariable(functionVariable);
function.setContext(functionContext);
// Add the function in the global context
globalVariable.put(function.getIdentifier(), function);
---
Are we done? Nearly. We have seen how the scope chain is normally
constructed, and it was quite a headache, wasn't it? Well, be informed
that the scope chain may also be altered during the script execution!
The specification defines two statements - the WithStatement and the
TryStatement - where an anonymous object is pushed onto the scope chain.
Let's study a WithStatement:
---
var a="hello, world!";
var obj={a:"hello, with!"};
with (obj) {
alert(a); // hello, with!
}
alert(a); // hello, world!
---
The "obj" object is temporarily inserted onto the scope chain when
inside a WithStatement, so the "a" identifier will be first resolved
against "obj" before being resolved against the global Variable Object.
When the WithStatement is exited, then the "obj" object is removed from
the scope chain.
It works similarly with a catch clause of a TryStatement, except that
the object inserted onto the Scope is an anonymous object, with one
entry whose key is set to the catch clause identifier, and whose value
is the exception thrown by the try block:
---
var a="hello, world!";
try {
throw "hello, catch!";
} catch (a) {
alert(a); // hello, catch!
}
alert(a); // hello, world!
---
If you have come there, then congratulations! As an exercise, check the
following script, and guess what each "dbg" call outputs, thinking
carefully about contexts, scopes and variables objects.
Note that IE9 javascript implementation has a bug: the catch clause does
not augment the scope chain correctly. Firefox and Chrome have it right.
Good luck.
---
<div id="dbg_box">DBG_BOX<br></div>
<script type="text/javascript">
// dbg - writes into the DBG_BOX
var dbgIndex=1;
function dbg(msg){
document.getElementById("dbg_box").innerHTML +=
"Case " + (dbgIndex++) + " = " + msg + "<br>";
}
//Test
var a="global";
dbg(a);
test();
dbg(a);
//Test function
function test() {
//~~~~ local variable
var a="function";
dbg(a);
//~~~~ with
with ({a:"with"}) {
dbg(a);
a="redefinedInWith";
dbg(a);
}
dbg(a);
//~~~~ try/catch
try {
throw "exception";
} catch (a) {
dbg(a);
a="redefinedInCatch";
dbg(a);
}
dbg(a);
inner("argument");
dbg(a);
//~~~~ inner function
function inner(a){
dbg(a);
a="inner";
dbg(a);
}
}
</script>