Why do we not receive an error when we call an undeclared variable as a window property?

Asked

Viewed 189 times

11

We initially have this:

var a = 0;
console.log(window.a === a); // true

Okay, so here we prove that when we declare a variable in global scope, it becomes a property of the object window.

Now, we have a few more scenarios.

var a = {};

console.log(a.foo); // a.foo não está declarado, entretanto recebemos 'undefined', ao invés do erro.

Now the question comes to me. Why, when I call for a without it being declared, I get error:

console.log(a);

But when I call window.a receive Undefined:

console.log(window.a)

Whereas window.a === a. Why don’t I get error in the last snippet? The same goes for why I don’t get error when I call var a = {}; console.log(a.foo), but yes undefined.

3 answers

8

We know that when we try to read a variable that is not available in the current scope the error (ReferenceError) is launched. To learn more, read this document.

We also know that when a variable is declared in the "global scope" using var, it also becomes available on object window (or global, if you are using Node.js). Thus, a variable will be available in the object window only if it is declared in the global scope using var (or any kind of declaration).

See on snippet hidden beneath this behavior:

window.a = 'a'; // Estará disponível em `window` (definido explicitamente).
console.log('a ->', window.a);

b = 'b'; // Estará disponível em `window`.
console.log('b ->', window.b);

var c = 'c'; // Estará disponível em `window`.
console.log('c ->', window.c);

(() => {
  var d = 'd'; // Não estará disponível em `window`, já que está em um outro escopo.
})();
console.log('d ->', window.d);

let e = 'e'; // Não estará disponível em `window`, já que usa `let`.
console.log('e ->', window.e);

let f = 'f'; // Não estará disponível em `window`, já que usa `let`.
console.log('f ->', window.f);

I can not deny, this type of behavior is strange and corroborates with the immense amount of criticism to Javascript.


In addition to these two concepts, to understand the difference of this question, we must have the knowledge of the following behavior of objects:

Undefined properties of an object are undefined. [Source]

This means that if you try to access a value of an object that has not been defined, it will return undefined:

const obj = {};
console.log(obj.iAmNotDefined); // undefined

This behaviour, documented in language specification, justifies the fact that no errors are made when trying to access a property that does not exist in an object. And there is the answer to your question! In view of the fact that window is an object, when trying to access a "global variable" (i.e., a property) that is not defined, the value undefined will be returned and no error will be fired.

To conclude, let’s compare the two approaches...

To reading of a variable undefined causes an error:

console.log(a); // Uncaught ReferenceError: a is not defined

The access to a property undefined returns undefined (does not throw any error, because it is not wrong):

console.log(window.a); // undefined


Stressing again, it is important to keep in mind that even if it is a same value, the error if gives in the way to recover it. The specification stipulates that reading an undefined variable should throw an error, but access an undefined property of an object (such as window) should not throw errors (see the comparison I made above).

Think of the difference between "reading" (the reading of the variable) and "access" (the access of a value in an object).

  • "Reading an undefined variable causes an error:" Ok, but the question is: every variable is a property of the window object, so actually calling it is calling by the window property, not?

  • @Máttheusspoo yes, that’s what’s in my answer.

  • 2

    Not every variable is a property of the object window. I will edit the answer by stressing this.

5


Access window.a and a when both are not defined generate distinct results because they are evaluated from distinct algorithms.

  • window.a is evaluated for the reference value a in the reference window;
  • a is evaluated using the reference value of a;

Reference

First, we need to understand how Javascript references each object, which occurs through an interface called Reference. An object Reference is composed of three parts:

  • The basic value;
  • The name of the reference;
  • A boolean flag that defines whether it is strict (Strict);

The base value of a reference may be undefined, an object, a boolean, a string, a symbol, a number or a record of environment. When the base value is undefined means that the object Reference cannot be solved.

With that in mind, we need to define some procedures that will be necessary to understand the algorithm further.

Accessing the object a

Thus, when the code will be analyzed the object is identified a internally a reference is defined (Reference) for it and as it is an object that cannot be solved, the value base will be as undefined. When executing a code that requires the value of a, just as console.log(a), the method will be executed GetValue(V) of the reference following the algorithm:

  1. ReturnIfAbrupt(V).
  2. If Type(V) is not Reference, Return V.
  3. Let base be GetBase(V).
  4. If IsUnresolvableReference(V) is true, throw a ReferenceError Exception.
  5. If IsPropertyReference(V) is true, then
    1. If HasPrimitiveBase(V) is true, then
      1. Assert: In this case, base will Never be undefined or null.
      2. Set base to !ToObject(base).
    2. Return ?base.[[Get]](GetReferencedName(V), GetThisValue(V)).
  6. Else,
    1. Assert: base is an Environment Record.
    2. Return ?base.GetBindingValue(GetReferencedName(V), IsStrictReference(V)).

In this case we have a baseline reference undefined for the object a, then in step 4, when the validation is done IsUnresolvableReference(V) is returned true and therefore launched the exception ReferenceError.

Accessing the object window.a

Similar to when the object is directly accessed a, so that the value of window.a references will also be defined internally, one for the object window and another for the object a. To the object a shall be a reference equal to the previous one, based on undefined, but the difference lies in the existence of reference to the object window which exists and can be resolved. Thus, different from the previous situation, in which the value of a is assessed from GetValue(V), the value of window.a will be evaluated from the function [[Get]](P, Receiver) of the interface Object, being P the reference to the object a and Receiver the reference to the object window. The algorithm executed for this will be:

  1. If P was Previously observed as a non-configurable, non-writable Own data Property of the target with value V, then [[Get]] must Return the SameValue as V.
  2. If P was Previously observed as a non-configurable Own accessor Property of the target Whose [[Get]] attribute is undefined, the [[Get]] Operation must Return undefined.

As the value P will be the reference to the object a, the basic value of which is undefined, step 2 will be executed, causing [[Get]] return undefined.

Do window.a is equal to a?

No, as shown, the values will be evaluated differently and produce different results.

But why then window.a === a is true?

Because when the variable is defined a reference to it is stored within the "global" object of the current scope. When defined in the overall scope of the application, this object will be the window. When it is defined in a limited scope, this object will be another (and I do not know if it is accessible at runtime as is the window).

To confirm this information just check the algorithm used when defining a variable by running PutValue(V, W):

  1. ReturnIfAbrupt(V).
  2. ReturnIfAbrupt(W).
  3. If Type(V) is not Reference, throw a ReferenceError Exception.
  4. Let base be GetBase(V).
  5. If IsUnresolvableReference(V) is true, then
    1. If IsStrictReference(V) is true, then
      1. Throw a ReferenceError Exception.
    2. Let globalObj be GetGlobalObject().
    3. Return ?Set(globalObj, GetReferencedName(V), W, false).
  6. Else if IsPropertyReference(V) is true, then
    1. If HasPrimitiveBase(V) is true, then
      1. Assert: In this case, base will Never be undefined or null.
      2. Set base to !ToObject(base).
    2. Let succeeded be ?base.[[Set]](GetReferencedName(V), W, GetThisValue(V)).
    3. If succeeded is false and IsStrictReference(V) is true, throw a TypeError Exception.
    4. Return.
  7. Else,
    1. Assert: base is an Environment Record.
    2. Return ?base.SetMutableBinding(GetReferencedName(V), W, IsStrictReference(V)).

That basically, when it’s done var a = 1, a reference to the object a, initially at base value undefined due to Hoisting.

Thus, running the algorithm, in step 5 IsUnresolvableReference(V) return true and thus, not being a strict reference, seek the reference to the global object GetGlobalObject(), which in this case, because it is a global variable will be the object window, and the field shall be defined GetReferencedName(V) with the value W. So when it’s done var a we will also have the value in window.a (or window['a']).

From this the reference of a can be resolved and the next times there is an assignment to the object a in the code step 6 will be executed, updating the value of it.

So var a = 1 is the same as doing window["a"] = 1?

No, although it produces the same result. As we have seen, when evaluating the expression var a = 1 the operation will be carried out Set(window, "a", 1, false), however the value "a" which will be the key in the object window is evaluated from the reference to the object a, through function GetReferencedName(V), while doing window["a"] = 1 this internal reference is not generated.

4

Javascript is a language full of oddities because it was set very quickly. The main bad decision was to have weak typing, and worse, they adopted very bad criteria to transform one type into another implicitly. One could argue that this makes it easier for the novice programmer, but it’s a shot in the marketing foot because then people will have to deal with the cost of this for the rest of their lives (to tell you the truth I don’t think they even believed it would work). So I expect everything confusing instead of a clear mistake where it can get messy. JS chooses to try to make it work even if it doesn’t work.

Then the === etá comparing properties of the object window, even if one side has not written explicitly, as the question itself indicates, a global variable enters as property of the object window.

That lack of criteria made it inconsistent, probably happened to be so, did not realize that this would happen.

Remembering that objects in JS are actually tables hash, so the ones that look like variables of an object are actually keys inside that table and if the key doesn’t exist it can’t say that the variable doesn’t exist, because deep down what you did is a search for a data inside that table and found nothing, is a data error and not syntax or semantics, the language does not know any of this. Could it be treated differently? It could, but it would lose some flexibility, and probably someone regretted it and wanted to change, but it was too late. It’s something cute, but it brings problems.

The object window has elements because it is a table hash, you try to access and receive that the data is undefined (it is an undefined value, but it is a value).

A real variable the language can determine that there is an error it is a syntactic element of the code not only a data contained in a collection that passes through a structured object. They decided in this case not to consider that it is also a property of window, although it is because the syntax does not indicate this. Since the language has chosen the path to place a global variable as part of the object window and that not having a name of this variable give an indefinite value, the odd is this behavior.

This can be best perceived in JS native objects are associative arrays?.

So when declares the global variable a it’s actually the same as doing

window["a"] = 0;

I put in the Github for future reference.

Obviously local variables do not happen this.

Then the more correct question would be:

Why error when trying to access an undefined global variable, since it is part of the object window, though implicitly, and that non-existent properties of an object result in undefined?

Only the creators of language can answer that.

The exact reason why they chose to do this is only who they chose and can say and can even be the famous "wanted like this", so it is speculated that the motivation may be the one I mentioned. It can be argued that the specification of the language has so determined and it can determine what it wants, no matter if it is coherent and makes sense.

Javascript is probably the language that is worth the "what matters is to work, not to be right" that pleases amateurs and beginners who will see result even if it brings problems, the worst is the inconsistency, you have to know the rules in each context not to have surprises, that is, the language that is easy to learn is difficult to use.

Fiat 147 todo detonado andando pelas ruas

Browser other questions tagged

You are not signed in. Login or sign up in order to post.