Being the hipster that I am, I'm just now getting around in 2009 to doing some work in JavaScript. To help me get up to speed, I begged the twitter-verse for help and my friend Glenn Vanderburg came to the rescue. He gave me a little personal tutorial on methods and namespace issues via IM, and when I asked if I could paste it here, he said, “Anything for you, Mr. Fowler.”
Glenn's written about his own n00by-ness with JavaScript here, an extract from some of his NFJS talks.
I guess my immediate n00b concerns: 1) I'm going to have a few methods all acting on some data, so I don't want 5 global methods all passing around the same data through params and 2) I've seen people do namespace tricks to keep from defining anything in the global space methods, but I don't know what those are or what is best.
OK. JavaScript supports “classes”, kinda. It uses functions to do double-duty as constructors, and if you intend to use a function as a constructor, conventionally you would give it an uppercase name.
function Point(x, y) { this.x = x; this.y = y; }
var p1 = new Point(3,4);
Then to add methods, you do this:
Point.prototype.distance = function(otherPoint) { whatever(); }
Now you can say
p1.distance(p2);
The constructor (Point) is a function, but a function is just an instance of class “Function”, so it's an object that has its own properties. ‘prototype’ is one of them.
So it's normal to define them outside the constructor, then?
Yes, that's normal. If you do that, then every instance shares the same methods. If you do it in the constructor, each instance gets its own (identical) copy of all the methods. That's not a big deal if you're only going to create one instance, which is pretty common, and it does have the advantage that the methods defined in the constructor share access to the parameters of the constructor and local vars defined in the constructor, which gives you a kind of “private” instance variable (Crockford writes about that). But typically you don't need that way. The JavaScript way is public everywhere, and in the context of a web page that works pretty well.
So back to my example of adding methods ...
Point.prototype.distance = function(otherPoint) { ... };
Since Point is just an object, and so is its prototype, you can do this also:
Point.prototype = {
distance: function(otherPoint) { ... };
};
replacing the entire prototype with a new object that has all of your methods defined in it. Some people like that because it's less noisy and gives you one set of braces between which you can find all of the methods of Point.
Ok. So if i did that a 2nd time, it'd blow away the prior methods, not append them.
Yes, precisely.
So then, others have defined methods like Object.extend so that you can say:
Point.prototype.extend( {
distance: function(otherPoint) { ... },
});
Extend copies all the properties from the passed object into the target object, so you don't actually replace the prototype, you just add a batch of new properties and values to it.
[You can also add methods to an object's prototype this way:
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g,"");
}
]
The cool thing about the way the prototype works, whether you use it the ‘normal’ way or using extend, is if you start with this:
Point p1 = new Point(3,4);
Point p2 = new Point(4,5);
and then, later, you do this:
Point.prototype.translate = function(otherPoint) {
this.x += otherPoint.x; this.y += otherPoint.y;
};
Now p1 and p2 both support translate(), even though you defined the method after you created the instances, because method lookup is completely dynamic.
This is similar to Ruby, no? If I append a method to the String class, all existing string instances get that method, too.
Yes. But in JavaScript, if you replace the prototype after you've created instances ... you've effectively created two classes. All of the previously created points are members of the first class, and new ones are members of the new one.
When you say “new Point(3,4)” the new point object gets created, and then its __parent__ property (which is where method lookups happen ... it's like the internal class pointer in Ruby) gets set based on the current value of Point.prototype.
Now, on to namespaces.
There's no such thing as a namespace. Just global variables, local variables, and objects. Sometimes people define a “namespace” by wrapping everything in a big anonymous function and using local vars:
(function() { var a, b, c; function foo(){ a = 3 } function bar(){ alert(a); } foo(); bar(); } )();
and there are times when that's appropriate. But usually people define one global variable, set it to an object, and use properties of that object as their namespace:
FooBar = { foo: function() { this.a = 3 }, bar: function() { alert(this.a); } };
That's what jQuery does: it only sets two variables in the local scope (jQuery and $) and they both contain the same object, which has a bunch of properties that are the jQuery functions and global state.
Ok. I think that broke parts of my brain.
Sorry about your brain. :-) JavaScript will do that.
Well, thanks a ton for the impromptu tutorial, this was very helpful.
You're welcome.
tags: ComputersAndTechnology