If I declare a global variable x as:
var x = "I am window.x";
x will be a public property of the window object.
If I call a global function (without using “call”, “apply” or attaching it to another object first), the window object will be passed in as the context (the “this” keyword).
It is like it is siting the x property on the current context, which happens to be the window.
If, however, I declare a variable in the same way inside a function, then use that function as a constructor, the property x will not be a public property of the object I just constructed (the current context). I am happy (I know I can do this.x = …), but it just seems like a bit of a contradiction.
Have I misunderstood something (about it being a contradiction / different behaviour)? Would anyone be able to explain what is going on, or is it just something I have to accept?
Hope that my question is clear.
It seems like you’ve understood it just fine (I pick one small nit with your terminology below). Local variables within constructor functions are just that: Local variables inside constructor functions. They’re not part of the instance being initialized by the constructor function at all.
This is all a consequence of how “scope” works in JavaScript. When you call a function, an execution context (EC) is created for that call to the function. The EC has something called the variable context which has a binding object (let’s just call it the “variable object,” eh?). The variable object holds all of the
vars and function arguments and other stuff defined within the function. This variable object is a very real thing and very important to how closures work, but you can’t directly access it. Yourxin the constructor function is a property of the variable object created for the call to the constructor function.All scopes have a variable object; the magic is that the variable object for the global scope is the global object, which is
windowon browsers. (More accurately,windowis a property on the variable object that refers back to the variable object, so you can reference it directly. The variable objects in function calls don’t have any equivalent property.) So thexyou define at global scope is a property ofwindow.That terminology nit-picking I promised: You’ve said:
Which is mostly true. E.g., if you call a global function like this:
…then yes,
thiswill be the global object (window) during the call. But there are lots of other ways you might call that global function where it won’t be. For instance, if you assign that “global” function to a property on an object and then call the function via that property,thiswithin the call will be the object the property belongs to:or you might use
callorapplyfeatures of function objects to setthisexplicitly:More to explore (disclosure: these are links to my blog, but it doesn’t have ads or anything, seems unlikely I’ll add them):
this)this(even more about functions andthis)Update: Below you’ve said:
You’re on the right track, and yes, there are always those two things kicking around (usually more; see below). But “scope” and
thishave nothing to do with each other. This is surprising if you’re coming to JavaScript from other languages, but it’s true.thisin JavaScript (which is sometimes called “context” although that can be misleading) is defined entirely by how a function is called, not where the function is defined. You setthiswhen calling a function in any of several ways (see answer and links above). From athisperspective, there is no difference whatsoever between a function defined a global scope and one defined within another function. Zero. Zilch.But yes, in JavaScript code (wherever it’s defined) there’s always
this, which may be anything, and a variable object. In fact, there are frequently multiple variable objects, arranged in a chain. This is called the scope chain. When you try to retrieve the value of a free variable (an unqualified symbol, e.g.xrather thanobj.x), the interpreter looks in the topmost variable object for a property with that name. If it doesn’t find one, it goes to the next link in the chain (the next outer scope) and looks on that variable object. If it doesn’t have one, it looks at the next link in the chain, and so on, and so on. And you know what the final link in the chain is, right? Right! The global object (window, on browsers).Consider this code (assume we start in global scope; live copy):
The output is this:
You can see the scope chain at work there. During a call to
callback, the chain is (top to bottom):callbackfoothat createdcallbackNote how the variable object for the call to
foolives on past the end of thefoofunction (fooreturns beforecallbackgets called bysetTimeout). That’s how closures work. When a function is created (note that a newcallbackfunction object is created each time we callfoo), it gets an enduring reference to the variable object at the top of the scope chain as of that moment (the whole thing, not just the bits we see it reference). So for a brief moment while we’re waiting our twosetTimeoutcalls to happen, we have two variable objects for calls tofooin memory. Note also that arguments to functions behave exactly likevars. Here’s the runtime of the above broken down:window,Date,String, and all the other “global” symbols you’re used to having).varstatements at global scope; initially they have the valueundefined. So in our case,alphaandbeta.undefined. So in our case,fooand my utility functionsdisplayandnewSection.var alpha = "I'm window.alpha";. It’s already done thevaraspect of this, of course, and so it processes this as a straight assignment.var beta = ....displaytwice (details omitted).foofunction declaration has already been processed and isn’t part of step-by-step code execution at all, so the next line the interpreter reaches is isfoo("I'm gamma1, passed as an argument to foo");.foo.foo#varobj1.foo#varobj1a copy offoo‘s reference to the variable object wherefoowas created (the global object in this case); this is its link to the “scope chain.”foo#varobj1for all named function arguments,vars, and function declarations insidefoo. So in our case, that’sgamma(the argument),alpha(thevar), andcallback(the declared function). Initially they have the valueundefined. (A few other default properties are created here that I won’t go into.)foo(in order, beginning to end). In our case, that’scallback:foo#varobj1)foo#varobj1foocodevar alpha = ...line, givingfoo#varobj1.alphaits value.newSectionand calls the function (details omitted, we’ll go into detail in a moment).alpha:foo#varobj1. Sincefoo#varobj1has a property with that name, it uses the value of that property.displayand calls it (details omitted).beta:foo#varobj1, butfoo#varobj1doesn’t have a property with that namefoo#varobj1for its reference to the next linkdisplaygammaand callsdisplay. This is exactly the same as foralphaabove.callback, finding it onfoo#varobj1setTimeout, finding it on the global objectsetTimeout, passing in the arguments (details omitted)foo. At this point, if nothing had a reference tofoo#varobj1, that object could be reclaimed. But since the browser’s timer stuff has a reference to thecallbackfunction object, and thecallbackfunction object has a reference tofoo#varobj1,foo#varobj1lives on until/unless nothing refers to it anymore. This is the key to closures.foo, which createsfoo#varobj2and another copy ofcallback, assigning that secondcallbacka reference tofoo#varobj2, and ultimately passing that secondcallbacktosetTimeoutand returning.callbackfunction we created infoocallback#varobj1) for the call; it assignscallback#varobj1a copy of the variable object reference stored on thecallbackfunction object (which is, of course,foo#varobj1) so as to establish the scope chain.alpha, oncallback#varobj1callback‘s codenewSection, which it doesn’t find oncallback#varobj1and so looks at the next link,foo#varobj1. Not finding it there, it looks at the next link, which is the global object, and finds it.alpha, which it finds on the topmost variable object,callback#varobj1beta, which it doesn’t find until it gets down to the global objectgamma, which it finds only one link down the scope chain onfoo#varobj1callbackcallbackfunction, which we created in our second call tofoo.callbackgets a reference tofoo#varobj2because that’s what’s stored on this particularcallbackfunction object. So (amongst other things) it sees thegammaargument we passed to the second call, rather than the first one.callbackfunction objects, they and the objects they refer to (includingfoo#varobj1,foo#varobj2, and anything their properties point to, likegamma‘s strings) are all eligible for garbage collection.Whew That was fun, eh?
One final point about the above: Note how JavaScript scope is determined entirely by the nesting of the functions in the source code; this is called “lexical scoping.” E.g., the call stack doesn’t matter for variable resolution (except in terms of when functions get created, because they get a reference to the variable object in scope when they were created), just the nesting in the source code. Consider (live copy):
What ends up getting output for
alpha? Right!"I'm window.alpha". Thealphawe define infoohas no effect whatsoever onbar, even though we calledbarfromfoo. Let’s quickly walk through:vars and declared functions.alphaits value.foofunction object, give it a reference to the current variable object (which is the global object), put it on thefooproperty.barfunction object, give it a reference to the current variable object (which is the global object), put it on thebarproperty.fooby creating an execution context and variable object. The variable object,foo#varobj1, gets a copy offoo‘s reference to its parent variable object, which is of course the global object.foo‘s code.bar, which it finds on the global object.barand its associated variable objectbar#varobj1. Assignbar#varobj1a copy ofbar‘s reference to its parent variable object, which is of course the global object.bar‘s code.alpha:bar#varobj1, but there’s no property with that name therebar, which is the global object. So it finds the globalalphaNote how
foo#varobj1isn’t linked at all tobar‘s variable object. And that’s good, because we’d all go nuts if what was in scope was defined by how and from where a function was called. 🙂 Once you understand that it’s linked to function creation, which is dictated by the nesting of the source code, it gets a lot easier to understand.And that’s why what’s in scope for
baris determined entirely by wherebaris in the source code, not how it got called at runtime.It’s not surprising that initially you were wondering about the relationship between
thisand variable resolution, because the global object (window) serves two unrelated purposes in JavaScript: 1. It’s the defaultthisvalue if a function isn’t called in a way that sets a different one (and at global scope), and 2. It’s the global variable object. These are unrelated aspects of what the interpreter uses with the global object for, which can be confusing, because whenthis===window, it seems like variable resolution relates in some way tothis, but it doesn’t. As soon as you start using something else forthis,thisand variable resolution are completely disconnected from one another.