Javascript factory pattern variable scoping
I am following a tutorial that is showing the factory pattern to create objects in javascript. The following code has me stumped as to why it works.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>6-2.htm</title>
</head>
<body>
<script type="text/javascript">
function createAddress(street, city, state, zip) {
var obj = new Object();
obj.street = street;
obj.city = city;
obj.state = state;
obj.zip = zip;
obj.showLabel = function() {
//alert(this.street + "\n" + this.city + ", " + this.state + " " + this.zip);
//var obj;
alert(obj.street + "\n" + obj.city + ", " + obj.state + " " + obj.zip);
};
return obj;
};
var JohnAddr = createAddress("12 A St.", "Johnson City", "TN", 37614);
var JoeAddr = createAddress("10061 Bristol Park", "Pensacola", "FL", 32503);
JohnAddr.showLabel();
JoeAddr.showLabel();
</script>
</body>
</html>
the first commented line seems proper to me (using the this
keyword in the showLabel function). i'm not sure how using obj in its place is working. obj would have to reference a global variable somewhere because within that function when its run there is no obj defined, right? since i make 2 objects, its not just luck in this case that both get displayed fine, the older values for obj's contents are stored and referenced properly. but how? if i uncomment the second comment th开发者_开发知识库en it breaks and i understand why, now im explicitly telling js that i'm talking about a local variable and there is none.
Welcome to the world of closures. You are right to sense what you feel to behave as if it's a global but not quite global. That's how closures behave.
Basically in javascript when a function returns not all local variables are necessarily garbage collected/freed like in Java or C. If there is a reference to that variable then that variable survives in the scope of the function where it is defined.
Technically the mechanics are different and some people try to explain it that way and end up confusing a lot of other people. To me, closures are a sort of 'private' global variables in that like globals, they are shared accross functions but they are not declared in the global scope. It's just like what you describe feeling upon encountering this feature.
Here are some of my other answers here on stackoverflow related to javascript closures which I believe are worth reading:
Hidden Features of JavaScript?
Please explain the use of JavaScript closures in loops
Or you can just google the phrase "javascript closure" to explore the subject.
Additional answer.
As for the explanation of why this
works in your code (as opposed to *cough* attempting to correct your code so that this
would work even though it works in the uncorrected version *cough* ;-):
Javascript has late bindings. Very late, very-very late. Not only is this
not bound during compile time, it is not even bound during run time. It is bound at execution time - that is, until a function is called you can't know what this really points to. The caller basically gets to decide what the value of this
is, not the function where this
is used.
Some funky javascript late binding maneuvers:
function foo () {
alert(this.bar);
}
var bar = "hello";
var obj = {
foo : foo,
bar : "hi"
};
var second_obj = {
bar : "bye"
};
foo(); // says hello, 'this' refers to the global object and this.bar
// refers to the global variable bar.
obj.foo(); // says hi, 'this' refers to the first thing before the last dot
// ie, the object foo belongs to
// now this is where it gets weird, an object can borrow/steal methods of
// another object and have its 'this' re-bound to it
obj.foo.call(second_obj); // says bye because call and apply allows 'this'
// to be re-bound to a foreign object. In this case
// this refers to second_obj
In your code, this
conveniently refers to the object invoking the function as its method which is why it works even though you are apparently not using supposedly correct constructor syntax.
The reason obj
works inside the showLabel
function is because obj is a local variable. The function is declared every time create address is called. In JavaScript we call this a closure.
Generally speaking the prototype object creation is preferred over this factory pattern.
Now have a look at this prototype example:
var Address = function(street, city, state, zip){
this.street = street;
this.city = city;
this.state = state;
this.zip= zip;
};
Address.prototype.showLabel = function(){
alert(this.street + "\n" + this.city + ", " + this.state + " " + this.zip);
}
Now when I create a new address with new keyword:
// create new address
var address = new Address('1', '2', '3', '4');
address.showLabel(); // alert
The code will behave exactly like you expect. However if I do not use the new keyword this
inside the constructor is actually the window
object.
// create new address
var address = Address('1', '2', '3', '4'); // address == undefined
window.showLabel(); // address was added to window
I hope this clears things up a little bit.
function Address(street, city, state, zip) {
this.street = street;
this.city = city;
this.state = state;
this.zip = zip;
this.showLabel = function() {
//alert(this.street + "\n" + this.city + ", " + this.state + " " + this.zip);
//var obj;
alert(this.street + "\n" + this.city + ", " + this.state + " " + this.zip);
};
};
var JohnAddr = new Address(...);
JohnAddr.showLabel();
This is part of the weirdness of js, showLabel is a closure and has access to obj because it was in scope when created - a new closure is created each time createAddress is called.
To use 'this' in the way you're expecting you'd need to use the new operator so:
var foo = new createAddress(...
And be assigning the member variables to 'this'.
In this case where new isn't used 'this' is the global object.
精彩评论