javascript: How to make a module to behave like and object and a function simultaneously?
I'm trying to build myself a little helper library. first, for learning purposes, then that later I can extend it so it may come in handy in projects.
I understand somewhat the prototype referencing, closures, and scoping. I also intentionally made it using modular pattern so my toolbox
is not polluting the global namespace.
I also know we may assign prototypes to constructor functions, so the constructed Objects will hold those methods.
Here's the content of toolbox.js
(function (window) {
var toolbox = (function () {
var toolbox = function(it){
return new pick(it);
}
var pick = function (it){
var craft = document.getElementsByTagName(it);
craft = Array.prototype.slice.call(craft, 0);
return Array.prototype.push.apply(this, craft);
}
pick.prototype = to开发者_JAVA技巧olbox.prototype = {
raw: function(){
return Array.prototype.slice.call(this, 0);
},
tell: function(secret){
return secret;
}
}
return toolbox;
}());
window.toolbox = toolbox;
})(window);
and calling the toolbox
:
toolbox("div"); //returns the desired object with the div collection
toolbox("div").raw(); //returns a raw array with the divs
toolbox().tell("A secret"); //returns "A secret"
toolbox.tell("It's a secret"); //type error: the function has no method like tell (like hell it does...should)
but modifying the above code like this:
var toolbox = (function () {
var toolbox = function(it){
return new pick(it);
}
...
toolbox.tell(secret){ return secret }
return toolbox;
}());
will work.
So my question is why toolbox.prototype = {}
doesn't do the trick, while pick.prototype = {}
will make pick
inherit the defined methods?
I'd like to achieve both toolbox.tell("something");
and toolbox("div").raw();
to be possible without having to resolve directly prototyping the method into the modul.
Please help! I've been googling for days to learn these, and I'm stuck now. Your help is much appreciated!
UPDATE
Here's how jQuery does this in a nutshell:
(function( window, undefined ) {
var jQuery = (function() {
// Define a local copy of jQuery
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
}
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
//init stuff
}
};
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function() {
//extend stuff
};
jQuery.extend({
//extend stuff
});
// Expose jQuery to the global object
return jQuery;
})();
window.jQuery = window.$ = jQuery;
})(window);
There's a difference when something is in a prototype and when it's on the object itself.
Consider the following example:
var foo = function() { return 'I am foo'; }
foo.prototype.lie = function() { return 'I am not foo'; }
foo.lie(); //error, lie does not exist in foo
var bar = new foo;
bar.lie(); //it works!
prototype
is basically the mid-way between the parent and the child. The prototype has all what the parent has, plus what you appended to it - however, the parent does not have its own prototype. And, to be more confusing, if you assign stuff to the parent object and not to its prototype, the extra stuff won't get copied to the children. Weird, right?
To explain the last statement (using the previous example):
foo.makeAPoem = function() { return 'Roses are red, violets are blue, coffe milk eggs'; }
var bar = new foo;
foo.makeAPoem(); //works
bar.makeAPoem(); //nope!
It's exactly why you don't have String.split
for example, but 'abcd'.split
- these methods exist in the prototype.
If you want to make something works in both the object and the prototype, assign it to both!
foo.jedi = foo.prototype.jedi = function() { return 'This is not the function you are looking for'; }
foo.jedi(); //works
bar.jedi(); //works!
So now, you can choose what gets inherited and what stays in the parent. If you want it only available to the parent, only append it to the parent. However, if you want it to only be available on the child (e.g. you don't have jQuery.andSelf
, but you do have jQuery().andSelf) append it to the prototype. If you want both, append it to both.
you can define a power constructor (I'm not sure it's really called that way =)), something like:
var toolbox = (function(){
// some private methods
var test_1 = function(){ alert("test_1"); };
var test_2 = function(){ alert("test_1"); };
// return needed methods and props into the global namespace:
return {
"test_1" : test_1,
"test_2" : test_2
}
})();
toolbox.test_1();
The reason for this is that toolbox was never instantiated as a constructor like pick was. Simple solution.
Instead of
pick.prototype = toolbox.prototype = {
....
...
return toolbox;
use just
pick.prototype = {
....
....
for(var attr in pick.prototype) {
toolbox[attr] = pick.prototype[i];
}
This will add the methods directly to toolbox
精彩评论