Difference between creating a global variable, vs one in a constructor function in JavaScript
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 var
s 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. Your x
in 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 window
on browsers. (More accurately, window
is 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 the x
you define at global scope is a property of window
.
That terminology nit-picking I promised: You've said:
If call a global function, the window object will be passed in as the context (the “this” keyword).
Which is mostly true. E.g., if you call a global function like this:
myGlobalFunction();
...then yes, this
will 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, this
within the call will be the object the property belongs to:
var obj = {};
obj.foo = myGlobalFunction;
obj.foo(); // `this` is `obj` within the call, not `window`
obj['foo'](); // Exactly the same as above, just different notation
or you might use call
or apply
features of function objects to set this
explicitly:
var obj = {};
myGlobalFunction.call(obj, 1, 2, 3); // Again, `this` will be `obj`
myGlobalFunction.apply(obj, [1, 2, 3]); // Same (`call` and `apply` just vary
// in terms of how you pass arguments
More to explore (disclosure: these are links to my blog, but it doesn't have ads or anything, seems unlikely I'll add them):
- Closures are not complicated (explores the scope chain and variable objects and such)
- Mythical methods (more about functions and
this
) - You must remember
this
(even more about functions andthis
)
Update: Below you've said:
I just want to check my understanding: In any scope (global or function) there are always 2 objects: a “this” object (what is that called?) and a “variable object”. In the global scope, these 2 objects are the same. In a function’s scope, they are different, and the “variable object” is not accessible. Is that correct?
You're on the right track, and yes, there are always those two things kicking around (usually more; see below). But "scope" and this
have nothing to do with each other. This is surprising if you're coming to JavaScript from other languages, but it's true. this
in 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 set this
when calling a function in any of several ways (see answer and links above). From a this
perspective, 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. x
rather than obj.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):
var alpha = "I'm window.alpha";
var beta = "I'm window.beta";
// These, of course, reference the globals above
display("[global] alpha = " + alpha);
display("[global] beta = " + beta);
function foo(gamma) {
var alpha = "I'm alpha in the variable object for the call to `foo`";
newSection();
// References `alpha` on the variable object for this call to `foo`
display("[foo] alpha = " + alpha);
// References `beta` on `window` (the containing variable object)
display("[foo] beta = " + beta);
// References `gamma` on the variable object for this call to `foo`
display("[foo] gamma = " + gamma);
setTimeout(callback, 200);
function callback() {
var alpha = "I'm alpha in the variable object for the call to `callback`";
newSection();
// References `alpha` on the variable obj for this call to `callback`
display("[callback] alpha = " + alpha);
// References `beta` on `window` (the outermost variable object)
display("[callback] beta = " + beta);
// References `gamma` on the containing variable object (the call to `foo` that created `callback`)
display("[callback] gamma = " + gamma);
}
}
foo("I'm gamma1, passed as an argument to foo");
foo("I'm gamma2, passed as an argument to foo");
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
function newSection() {
document.body.appendChild(document.createElement('hr'));
}
The output is this:
[global] alpha = I'm window.alpha [global] beta = I'm window.beta -------------------------------------------------------------------------------- [foo] alpha = I'm alpha in the variable object for the call to `foo` [foo] beta = I'm window.beta [foo] gamma = I'm gamma1, passed as an argument to foo -------------------------------------------------------------------------------- [foo] alpha = I'm alpha in the variable object for the call to `foo` [foo] beta = I'm window.beta [foo] gamma = I'm gamma2, passed as an argument to foo -------------------------------------------------------------------------------- [callback] alpha = I'm alpha in the variable object for the call to `callback` [callback] beta = I'm window.beta [callback] gamma = I'm gamma1, passed as an argument to foo -------------------------------------------------------------------------------- [callback] alpha = I'm alpha in the variable object for the call to `callback` [callback] beta = I'm window.beta [callback] gamma = I'm gamma2, passed as an argument to foo
You can see the scope chain at work there. During a call to callback
, the chain is (top to bottom):
- The variable object for that call to
callback
- The variable object for the call to
foo
that createdcallback
- The global object
Note how the variable object for the call to foo
lives on past the end of the foo
function (foo
returns before callback
gets called by setTimeout
). That's how closures work. When a function is created (note that a new callback
function object is created each time we call foo
), 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 two setTimeout
calls to happen, we have two variable objects for calls to foo
in memory. Note also that arguments to functions behave exactly like var
s. Here's the runtime of the above broken down:
- The interpreter creates the global scope.
- It creates the global object and populates it with its default set of properties (
window
,Date
,String
, and all the other "global" symbols you're used to having). - It creates properties on the global object for all
var
statements at global scope; initially they have the valueundefined
. So in our case,alpha
andbeta
. - It creates properties on the global object for all function declarations at global scope; initially they have the value
undefined
. So in our case,foo
and my utility functionsdisplay
andnewSection
. - It processes each function declaration at global scope (in order, top to bottom):
- Creates the function object
- Assigns it a reference to the current variable object (the global object in this case)
- Assigns the function object to its property on the variable object (again, the global object in this case)
- The interpreter begins executing the step-by-step code, at the top.
- The first line it reaches is
var alpha = "I'm window.alpha";
. It's already done thevar
aspect of this, of course, and so it processes this as a straight assignment. - Same for
var beta = ...
. - It calls
display
twice (details omitted). - The
foo
function 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");
. - It creates an execution context for the call to
foo
. - It creates a variable object for this execution context, which for convenience I'll call
foo#varobj1
. - It assigns
foo#varobj1
a copy offoo
's reference to the variable object wherefoo
was created (the global object in this case); this is its link to the "scope chain." - The interpreter creates properties on
foo#varobj1
for all named function arguments,var
s, 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.) - It assigns the properties for the function arguments the values passed to the function.
- It processes each function declaration in
foo
(in order, beginning to end). In our case, that'scallback
:- Creates the function object
- Assigns that function object a reference to the current variable object (
foo#varobj1
) - Assigns the function object to its property on
foo#varobj1
- The interpreter begins step-by-step execution of the
foo
code - It processes the assignment from the
var alpha = ...
line, givingfoo#varobj1.alpha
its value. - It looks up the free variable
newSection
and calls the function (details omitted, we'll go into detail in a moment). - It looks up the free variable
alpha
:- First it looks on
foo#varobj1
. Sincefoo#varobj1
has a property with that name, it uses the value of that property.
- First it looks on
- It looks up
display
and calls it (details omitted). - It looks up the free variable
beta
:- First it looks on
foo#varobj1
, butfoo#varobj1
doesn't have a property with that name - It looks up the next link in the scope chain by querying
foo#varobj1
for its reference to the next link - It looks on that next link (which happens to be the global object in this case), finds a property by that name, and uses its value
- First it looks on
- It calls
display
- It looks up
gamma
and callsdisplay
. This is exactly the same as foralpha
above. - It looks up the free variable
callback
, finding it onfoo#varobj1
- It looks up the free variable
setTimeout
, finding it on the global object - It calls
setTimeout
, passing in the arguments (details omitted) - It returns out of
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 thecallback
function object, and thecallback
function object has a reference tofoo#varobj1
,foo#varobj1
lives on until/unless nothing refers to it anymore. This is the key to closures. - Wash/rinse/repeat for the second call to
foo
, which createsfoo#varobj2
and another copy ofcallback
, assigning that secondcallback
a reference tofoo#varobj2
, and ultimately passing that secondcallback
tosetTimeout
and returning. - The interpreter runs out of step-by-step code to execute and goes into its event loop, waiting for something to happen
- About 200 milliseconds go by
- The browser's timer stuff tells the interpreter it needs to call the first
callback
function we created infoo
- The interpreter creates an execution context and associated variable object (
callback#varobj1
) for the call; it assignscallback#varobj1
a copy of the variable object reference stored on thecallback
function object (which is, of course,foo#varobj1
) so as to establish the scope chain. - It creates a property,
alpha
, oncallback#varobj1
- It starts step-by-step execution of
callback
's code - You know what happens next. It looks up various symbols and calls various functions:
- Looks up
newSection
, which it doesn't find oncallback#varobj1
and 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. - Looks up
alpha
, which it finds on the topmost variable object,callback#varobj1
- Looks up
beta
, which it doesn't find until it gets down to the global object - Looks up
gamma
, which it finds only one link down the scope chain onfoo#varobj1
- Looks up
- The interpreter returns from the call to
callback
- Almost certainly, there in its event queue there's a message from the browser waiting for it, telling it to call the second
callback
function, which we created in our second call tofoo
. - So it does it all again. This time, the variable object for the call to
callback
gets a reference tofoo#varobj2
because that's what's stored on this particularcallback
function object. So (amongst other things) it sees thegamma
argument we passed to the second call, rather than the first one. - Since the browser has now released its references to the two
callback
function 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):
var alpha = "I'm window.alpha";
function foo() {
var alpha = "I'm alpha in the variable object for the call to `foo`";
bar();
}
function bar() {
display("alpha = " + alpha);
}
foo();
What ends up getting output for alpha
? Right! "I'm window.alpha"
. The alpha
we define in foo
has no effect whatsoever on bar
, even though we called bar
from foo
. Let's quickly walk through:
- Set up global execution context etc. etc.
- Create properties for the
var
s and declared functions. - Assign
alpha
its value. - Create the
foo
function object, give it a reference to the current variable object (which is the global object), put it on thefoo
property. - Create the
bar
function object, give it a reference to the current variable object (which is the global object), put it on thebar
property. - Call
foo
by 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. - Start step-by-step execution of
foo
's code. - Look up the free variable
bar
, which it finds on the global object. - Create an execution context for the call to
bar
and its associated variable objectbar#varobj1
. Assignbar#varobj1
a copy ofbar
's reference to its parent variable object, which is of course the global object. - Start step-by-step execution of
bar
's code. - Look up the free variable
alpha
:- First it looks on
bar#varobj1
, but there's no property with that name there - So it looks at the next link, which is the link it got from
bar
, which is the global object. So it finds the globalalpha
- First it looks on
Note how foo#varobj1
isn't linked at all to bar
'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 bar
is determined entirely by where bar
is in the source code, not how it got called at runtime.
It's not surprising that initially you were wondering about the relationship between this
and variable resolution, because the global object (window
) serves two unrelated purposes in JavaScript: 1. It's the default this
value 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 when this
=== window
, it seems like variable resolution relates in some way to this
, but it doesn't. As soon as you start using something else for this
, this
and variable resolution are completely disconnected from one another.
Your understanding of properties and constructors is fine; the concepts you're missing are 'scopes' and 'closures'. This is where var
comes into play.
Try reading Robert Nyman's explanation
You have some examples in this fiddle :
var x = 42;
function alertXs() {
this.x = 'not 42'; // this = window
var x = '42 not'; // local x
alert('window.x = ' + window.x); // 'not 42'
alert('this.x = ' + this.x); // 'not 42'
alert('x = ' + x); // '42 not'
}
alertXs();
http://jsfiddle.net/Christophe/Pgk73/
Sometimes, creating tiny fiddles helps to understand...
But you are aware with local and public variable as you explain that very well...
精彩评论