I'm attempting to build my first (browser) library, and chose to use the node-style commonjs modules + browserify to organize my code. I've structured it so each module contains 1 class, which worked out pretty well until a few of the classes started getting eye-bleedingly huge.
So I let's say I have a class like
module.exports = MyClass;
function MyClass() {
//initializing stuff
this.publiceMethod= function publicMethod() {
//do stuff
};
var privateMethod = function privateMethod() {
//do stuff
};
}
MyClass.prototype.notSureMethod = function notSureMethod() {
//err... static method?
}
The problem is that I have a large amount of methods using various ways of declaring them ( this.method, var method, this.prototype.method). What I'm wondering is if there is a relatively simple way to create another module(s) and require them into MyClass as part of the class definition in order to increase readability.
The ideal behavior that I'm looking for is something like a module that is a self-executing function that shares the same scope of what it's called from (including private variables / methods if possible).
I've only been learning JS for a few weeks now, so go easy on me if I'm being idiotic. Thanks in advance :)
--edit--
Blindly playing around with it, I've figured out how to do what I'm looking for with prototype and public methods.
//Underneath MyClass definition
require('./prototype-methods')(MyClass);
require('./public-methods')(MyClass);
Then the gist of it in other files is:
module.exports = function(MyClass) {
MyClass.prototype.method = . . . .
MyClass.method = . . . .
}
So that leaves me wondering if there is a way to do something similar with private methods. Any ideas?
---edit2---
What exactly is you use case? Could you give an example of what your class does?
I'm making a library for the web audio api that essentially lets you compose music. I have a namespace for the library, and the namespace (at the moment) contains a few different classes. There is the score class, which acts as a mediator between the part, effect and player classes. The part class is just a wrapper for an instrument, which is an object with functions that play music.
The namespace has a factory function that returns a new instance of score, which in turn has a factory functions that return new part(s), effect(s), and eventually the player. Also, there can be more than 1 score at any given time so I could potentially make a playlist.
I initially tried to use a purely functional pattern, but my code took on a new meaning of spaghetti. I'm more familiar with namespacing / classes than I am functional patterns. Overall, I've been building the library just as I would a normal javascript file, but due to the added complexity, have been using commonjs modules + browserify to easily split the code into different files / build the parts.
The specific class this question is about is the part class. I'm wanting the library to be very flexible in what notations it accepts, so I needed to add quite a few methods to account for all of those notations (= big file).
I've found that I can pretty much add everything except for private members to MyClass with a module by passing MyClass to the module as a parameter.
It seems that I could technically add / access private members using eval, but I've been told it is evil. Just taking the prototype and public methods out of the file trimmed it down significantly, so leaving the private members in there is perfectly fine.
isbn 978-0-596-80675-0 (Chapter 5 Object Creation Patterns / Sandbox Pattern), isbn 0-596-10199-6 (Chapter 10 Modules and Namespaces / Module Utilities)
At the moment, I'm as well in search of convenient method of creating a privat data. I currently use a functional pattern of inheritance and defineClass like that:
var defineClass = function () {
var inheritance = function inheritance() { };
return function defineClass(data) {
var classname = data.name,
superclass = data.extend || Object,
constructor = data.construct || function () { },
methods = data.methods || {},
statics = data.statics || {},
borrows,
provides;
if (!data.borrows) {
borrows = [];
}
else {
if (data.borrows instanceof Array) {
borrows = data.borrows;
}
else {
borrows = [data.borrows];
};
};
if (!data.provides) {
provides = [];
}
else {
if (data.provides instanceof Array) {
provides = data.provides;
}
else {
provides = [data.provides];
};
};
inheritance.prototype = superclass.prototype;
var proto = new inheritance();
for (var i = 0; i < borrows.length; i++) {
var c = borrows[i];
for (var p in c.prototype) {
if (typeof c.prototype[p] != "function") continue;
proto[p] = c.prototype[p];
}
}
for (var p in methods) {
proto[p] = methods[p];
};
proto.constructor = constructor;
constructor.superclass = superclass.prototype;
if (classname) {
proto.classname = classname;
};
for (var i = 0; i < provides.length; i++) {
var c = provides[i];
for (var p in c.prototype) {
if (typeof c.prototype[p] != "function") {
continue;
};
if (p == "constructor" || p == "superclass") {
continue;
};
if (p in proto && typeof proto[p] == "function" && proto[p].length == c.prototype[p].length) {
continue;
};
throw new Error("Class " + classname + " are not provided method " + c.classname + "." + p);
};
};
constructor.prototype = proto;
for (var p in statics) {
constructor[p] = statics[p];
};
return constructor;
}
}();
//SAMPLE CODE
var tempObj = function () { };
// example of a variable
tempObj.prototype.distance = 0;
// example of a method
tempObj.prototype.walk = function (time) {
this.distance = this.distance + time * this.walkSpeed
};
tempObj.prototype.toString = function () {
return this.name + " distance " + this.distance
};
var Animal = defineClass({
name: "Animal",
construct: function (name, walkSpeed) {
this.name = name;
this.walkSpeed = walkSpeed;
},
borrows: tempObj,
methods: {
distance: tempObj.prototype.distance
}
});
var tempObj2 = defineClass({
methods: {
fly: function (time) {
this.distance = this.distance + time * this.flySpeed
}
}
});
var Bird = defineClass({
name: "Bird",
construct: function (name, walkSpeed, flySpeed) {
// call the parent constructor
Bird.superclass.constructor.call(this, name, walkSpeed)
this.flySpeed = flySpeed
},
extend: Animal,
borrows: tempObj2
});
var Cuckoo = defineClass({
name: "Cuckoo",
extend: Bird,
construct: function (name, walkSpeed, flySpeed) {
// call the parent constructor
Cuckoo.superclass.constructor.call(this, name, walkSpeed, flySpeed)
this.word = "cucoo";
},
methods: {
say: function () {
return this.name + " says " + this.word;
}
}
});
var animal = new Animal("Dog", 2);
animal.walk(3);
var dd = animal.toString(); // => Dog distance 6
bird = new Bird("Bird", 1, 10);
bird.walk(3);
var ww = bird.toString(); // => Bird distance 3
bird.fly(2);
var ff = bird.toString(); // => Bird distance 23
cuckoo = new Cuckoo("Cuckoo", 1, 10);
cuckoo.walk(3);
var ww = cuckoo.toString(); // => Cuckoo distance 3
cuckoo.fly(2);
var ff = cuckoo.toString(); // => Cuckoo distance 23
var cSay = cuckoo.say(); // => Cuckoo says cucoo
What exactly is you use case? Could you give an example of what your class does?
Because going the object-oriented way is not alway the best way to do things in JavaScript. Node-style modules and the fact that functions are first-class citizens in js make it really easy to go the functional way. Meaning that you can export single functions which do exactly one thing. For example:
module.exports = function(x, y) {
return x * y
}
And in a second module you can use this function like this:
var add = require('./add.js')
var result = add(15, 23);
If you want to learn more about this, check out this nodeschool workshop.