Using undefined variables in JScript

I got a question the other day pointing out that in JScript, it is legal to assign a value to an undeclared variable, and the variable kind of gets implicitly declared for you, but it is not legal to read the value of an undeclared variable. The writer wanted to know if y = 1; for some undeclared variable has exactly the same semantics as var y = 1;.

Close, but not quite. Assigning to an undefined variable is not equivalent to replacing the assignment with a declaration because that might declare a local variable bound to a particular activation. Using a variable without declaring it in Jscript produces a new global variable, not a new local variable. You can clearly see that they are not equivalent because these two programs do different things:

function foo() {y = 1; }
print(y); // 1

function foo() {var y = 1; }
print(y); // error, y undefined

These behaviours of the Jscript engine are conformant to the ECMAScript specification, Revision 3, Section 8.7.

In VBScript, on the other hand, using an undefined variable does produce a new local variable, whether you’re reading or writing.

The writer then went on to ask “how can I prevent JScript from creating a new variable in this case? I want this to be an error.”

To explain, we should take a look at what exactly happens when we see y = 1; so that we can determine that we need to create a new variable. To do the assignment successfully, three things must happen: we must determine an address for y, we must determine the value, and we must store the value in that address. The way those things happen goes like this:

  • First we search the scope chain – with blocks, the activation frame, the globals – for any object that has a property y. We do not find one.
  • Next we search all the global host objects asking them if they have a property y. We do not find one.
  • Therefore we create a special “fake” address and put it on the script stack. This completes the first part of the binding.
  • Determining the value is trivial in this case.
  • When we do the store we detect that there’s a fake address on the stack, allocate a new global variable in the script engine’s global name table, and replace the fake address with this new real address.
  • Finally, we invoke the property put logic in the global name table to store the value to the correct address.

Notice that the only question we ask the host is “do you have this property?”, and if the host says no, we create a variable. Therefore, if you want this to be an error, you have to write a script host that lies to the script engine. If you always say “yes, I have that property” even when you don’t, then you can raise an exception when the store logic attempts to do the store. However, I’ve never heard of anyone trying that. There might be other negative consequences to consistently lying to the script engine about whether you have a property or not. You’ll have to try it and see.

As an aside, you might be wondering why we go to the trouble of creating a fake address during the address lookup that gets turned into a real address during the store. Why not just create the real address in the first place?

Ponder that for a moment. Under what circumstances would we not want to create a real global variable until after the right hand side of the assignment was evaluated?

Well, you would not expect this program to produce a new global variable:

function foo() { try { y = eval(“throw 1;”); } catch(e) { } }
print(y); // should be undefined!

The global variable creation must happen at the moment of actual assignment, not at the moment when the address is determined, because determining the value might prevent the store from ever happening.

Comments (5)

  1. bmm6o says:

    I understand the point you make at the end, but why do you have to do the lookup before you have a value to store?  Is there a reason you can’t evaluate the right side first, and then look up y?

  2. EricLippert says:

    There are three things that have to happen in the right order:

    1) Evaluate the LHS.  (Where’s it going?)

    2) Evaluate the RHS. (What’s going there?)

    3) Put it there, creating "there" if necessary.

    I was asking the question "why does #3 have to happen after #2?"  

    Because if #2 produces an exception, then #3, which potentially has the side effect of altering the global name table, must never happen.

    You are asking the question "why does #2 have to happen after #1?", and the answer is pretty much the same == because evaluating the left hand side can produce arbitrary errors and those errors must happen BEFORE any side effects produced by evaluating the RHS.  When we ask the host "do you have y?" the host is free to say "throw an exception" rather than simply saying "nope", and if the host does that, then we need to make sure that any exception catcher runs without the side effects of the right hand side having happened.

  3. Pete T says:

    Slightly off-topic, but could you clarify:

    1. Is IE 7 due to include JScript.Net (i.e. client access to the frameworks from page scripts)?

    2. Is JScript.Net an officially deprecated language on the server, or is it just not a priority for Microsoft?

    Personally I think the ability to use the same syntax on client and server is great for UI Coders who need to code in both environments to produce a modern, efficient UI.

  4. bmm6o says:

    Ah, of course.  Once you expanded it to "evaluate the LHS" the light bulb went off (or on, rather).  I was stuck on thinking of doing just a global variable lookup.

  5. EricLippert says:

    1) Not to my knowledge, but I am on neither the IE nor the scripting teams anymore, so I am not privy to their strategic or tactical planning.

    2) No, it is not deprecated to my knowledge, and I am hoping that we will be making it a higher priority to invest in JScript .NET in several areas — I would also like to see JS.NET on the IE client, as well as support for e4x features and new CLR features such as generics.  However, note that this is my personal hope, and not based on any information one way or the other, see #1.