Namespacing, OOP JS, Am I doing this right?
My problem is with objInfo(). How can I return an object via 开发者_运维技巧a passed-in variable? I'm trying to namespace my code and use private/public vars.
Bonus Q: How would you otherwise improve the code?
// Namespace all my code
var bab = new function() {
// Declare cat object
function cat()
{
this.eyes = 2;
this.legs = 4;
this.diet = 'carnivore';
return true;
}
// Declare lion object
function lion()
{
this.mane = true;
this.origin = 'Africa';
this.diet = 'people'; // has priority over cat's diet
return true;
}
// Make lion a subclass of cat
lion.prototype = new cat();
// Create an instance of class lion
var simba = new lion();
// Share diet publicly
this.objInfo = function(name) {
return name; // simba works, name doesn't
};
};
alert(bab.objInfo('simba').diet);
Note: Source is sampled from various places
Other than namespacing, it's not clear to me what you're trying to do, but I've included a code review of sorts under the divider below. More high-level comments first.
There are a couple of issues here. First off, you almost never want to write new function() { }
. It's a very advanced technique that's easy to get wrong (and very easy for anyone doing maintenance on the code to misunderstand). There's an example of another, less-confusing way to get the same effect (plus some other benefits) below.
Here's an example of a namespaced module providing two "classes", Cat
and Lion
(I've made them initially-capped because that's the usual convention: initial caps on constructor functions and initial lower case on non-constructor functions, just to make it easy to read code):
var Animals = (function() {
var publics = {};
// A Cat
publics.Cat = Cat;
function Cat() {
this.eyes = 2;
this.legs = 4;
this.diet = 'carnivore';
}
// A Lion
publics.Lion = Lion;
function Lion() {
this.mane = true;
this.origin = 'Africa';
this.diet = 'people'; // has priority over cat's diet
}
Lion.prototype = new Cat();
// Return our public symbols
return publics;
})();
// Usage
var l = new Animals.Lion();
alert(l.eyes); // alerts "2" (inherited from Cat)
alert(l.diet); // alerts "people" (overridden by Lion)
(You can, of course, call publics
anything else you want — pubs
, p
, whatever. It's the equivalent of this
in the outermost layer of your new function() { }
function, but less confusing.)
But just replacing the prototype on Lion
is a bit simplistic. When you start getting into subclassing, there are several other things you want to look at doing. Here's a blog post that details a fairly complete means of building classes, including subclassing, calling superclass functions, etc.
In terms of looking something up by string, you can do that with brackets notation on any object:
var obj = {};
obj.foo = 42;
alert(obj["foo"]); // alerts "42" by retrieving the property "foo" from `obj`
var x = "f" + "o" + "o";
alert(obj[x]); // alerts "42" by retrieving the property "foo" from `obj`
Code review follows.
Here's a code review:
// Namespace all my code
// [TJC] Use the (function() { ... })(); mechanism described above rather than
// `new function() { ... }`, which is fairly confusing to the reader and troublesome
// to use inside inner functions (see below)
var bab = new function() {
// Declare cat object
// [TJC] Convention is to use initial caps for constructor functions,
// e.g. "Cat" not "cat"
function cat()
{
this.eyes = 2;
this.legs = 4;
this.diet = 'carnivore';
// [TJC] Don't return anything out of constructor functions
return true;
}
// Declare lion object
// [TJC] "Lion" rather than "lion" would be more conventional
function lion()
{
this.mane = true;
this.origin = 'Africa';
this.diet = 'people'; // has priority over cat's diet
// [TJC] Don't return anything out of constructor functions
return true;
}
// Make lion a subclass of cat
// [TJC] There are several other things you want to consider in
// addition to replacing the prototype
lion.prototype = new cat();
// Create an instance of class lion
// [TJC] From your usage below, it looks like you
// want to be able to look up "simba" using a string
// later. So use the below rather than this commented-out
// line:
//var simba = new lion();
var instances = {}; // [TJC]
instances.simba = new lion(); // [TJC]
// Share diet publicly
// [TJC] You don't need a function for this at all, just
// expose "instances" directly. But if you want it:
this.objInfo = function(name) {
// [TJC] To look up something by name using a string,
// use brackets:
//return name; // simba works, name doesn't
return instances[name]; // [TJC]
};
};
alert(bab.objInfo('simba').diet);
You have essentially made all but objInfo()
in bab
obsolete, since objInfo()
simply returns exactly what is passed into it.
In your specific case, objInfo("simba")
doesn't work since objInfo()
simply returns the string "simba"
:
...
// Share diet publicly
this.objInfo = function(name) { // <-- If name == "simba"
return name; // <-- This will return "simba" not the Object simba!!!
};
};
alert(bab.objInfo('simba').diet); // This will check for the diet property of
// the string "simba". So it won't work.
However there is a larger problem, as I mentioned before. objInfo()
simply returns exactly what is passed into it!
Try out these examples:
alert(bab.objInfo('simba')); // This will alert "simba"
alert(bab.objInfo('noodles')); // This will alert "noodles"
alert(bab.objInfo(window).innerWidth); // This will give you the innerWidth of
jsFiddle example of alert(bab.objInfo(window).innerWidth);
You have essentially "short circuited" your entire bab
object. Only the objInfo
method "does" anything.
This is how I would do it:
// Namespace all my code
var bab = new function() {
var cat = function() // Declare cat object
{
var protected = {}; // Protected vars & methods
protected.eyes = 2;
protected.legs = 4;
protected.diet = 'carnivore';
return protected; // Pass protected to descendants
}
var lion = function()
{
var protected = cat(); // Make lion a subclass of cat
var public = {}; // Public vars & methods
public.legs = protected.legs; // Make 1 protected var public
public.mane = true;
public.origin = 'Africa';
public.diet = 'people'; // has priority over cat's diet
return public; // Make public vars available
}
var simba = lion(); // Create an instance of class lion
simba.diet = "Asparagus"; // Change simba, but not lion
// Get property of choice
this.objInfo = function(property) {
return ("Simba: " + simba[property] +
" - Lion (this is usually private. Shown for testing.): " +
lion()[property]);
};
};
alert(bab.objInfo("diet"));
jsFiddle example
I used functional inheritance in the above. I find it simpler to work with and it makes good use of the classless nature of Javascript's Object Oriented persona.
As test output, I returned directly from lion
... which you would not usually do, just to show that changing Simba
doesn't change lion
. You can tell that the lion
's diet has priority over the cat
's diet, just like you wanted.
The trick is to package your protected
and public
variables and methods in returned objects, and don't forget that you can also create methods in you felines.
You could use eval
, but I would hate to recommend that.
You might improve the script by "registering" your lions in an array.
// Namespace all my code
var bab = (function() {
// Declare cat object
function cat() {
this.eyes = 2;
this.legs = 4;
this.diet = 'carnivore';
return true;
}
// Declare lion object
function lion() {
this.mane = true;
this.origin = 'Africa';
this.diet = 'people'; // has priority over cat's diet
return true;
}
// Make lion a subclass of cat
lion.prototype = new cat();
// Create an instance of class lion
// var simba = new lion();
var lions = {}; // Create a "lions" object to collect all of the lion instances
lions["simba"] = new lion();
return {
// Share diet publicly
objInfo: function(name) {
return lions[name];
};
}
})();
alert(bab.objInfo('simba').diet);
I've edited the code to use a less error-prone form of encapsulation. This article helped me a lot: Public and Private in JavaScript.
精彩评论