I'm trying to write a JavaScript library to allow inheritance with knockout.
The fundamental problem when implementing inheritance in Knockout is that each Knockout observable is its own instance, thus if you try this:
x = function () {};
x.prototype.m = ko.observable();
y = function () {};
y.prototype = new x();
z = new y();
//z.m == y.prototype.m == x.prototype.m
The behavior I'm trying to get goes like this:
//psudo-code, I know this wouldn't work
x = function () {};
x.prototype.a = ko.observable();
y = function () {};
y.prototype = new x();
y.prototype.b = ko.computed(function () { return this.a() });
//enabling this:
z = new y();
z.a(5);
a = new y();
a.a(12);
// z.b() == 5, a.b() == 12
Here's my proposed solution:
var getParams = function (args, index) {
return args.length > index + 1 ? Array.prototype.slice.apply(index) : Array.prototype.concat.call([], args[index])
};
var Ladder = function () {};
Ladder.prototype.init = function (config) {
var self = this;
if (!(config instanceof dontInit)) {
// Only initialize if config is passed, this allows us to create sub-prototypes
if (this.attachEntity) this.entity = config;
for (var prop in self) {
var extender = self[prop];
if (extender instanceof doAttach) {
self[prop] = extender.extender.apply(self, extender.args); // || ('' + extender.extender); // if the function doesn't return a value perhaps it only performs operations? Leave a tatle tale behind?
}
}
if (this.populateObject) {
for (var prop in config) {
if (config.hasOwnProperty(prop)) {
var val = config[prop];
if (ko && ko.isObservable(self[prop])) self[prop](ko.unwrap(val));
else self[prop] = val;
}
}
}
for (var prop in self) {
var extender = self[prop];
if (extender instanceof doExtend) {
self[prop] = extender.extender.apply(self, extender.args); // || ('' + extender.extender); // if the function doesn't return a value perhaps it only performs operations? Leave a tatle tale behind?
}
}
}
}
Ladder.prototype.attachEntity = true;
Ladder.prototype.populateObject = true;
Ladder.prototype.attach = function (propertyName, extender) {
var args = getParams(arguments, 2);
this.prototype[propertyName] = new doAttach(extender, args);
}
Ladder.prototype.extend = function (propertyName, extender) {
var args = getParams(arguments, 2);
this.prototype[propertyName] = new doExtend(extender, args);
}
Ladder.inherit = Ladder.prototype.inherit = function () {
var ctor = function (config) {
this.init(config);
};
ctor.attach = Ladder.prototype.attach;
ctor.extend = Ladder.prototype.extend;
ctor.inherit = Ladder.prototype.inherit;
ctor.prototype = new this(new dontInit());
return ctor;
};
Ladder.prototype.attachComputed // doComputed
Ladder.createPrototypes = function (prototypes) {
return duplicatePrototypes(prototypes);
}
var dontInit = function () {};
// doAttach is called before config and should attach a computed or observable, though you may attach any value you like.
var doAttach = function (extender, args) {
this.extender = extender;
this.args = args;
};
var doComputed = function (computed) {
return new doExtend(function () {
return ko.computed(computed, this);
}, []);
};
// doExtend is called after config and would normally perform some init work
// naming convention for extenders is _00Name where 00 is a priority (00 will run before 01) and Name is a descriptive name
var doExtend = function (extender, args) {
this.extender = extender;
this.args = args;
};
My questions are:
Are there performance issues with this type of inheritance? i.e. if I use this paradigm to build complex objects with long inheritance chains, will there be a performance hit vs some other 'better' inheritance paradigm?
Does this make for clear code? Is it obvious what is going on when I define an object like this:
var myObject = usefullBaseClass.inherit(); myObject.attach("newProperty", ko.observable, 5); var instanceOfMyObject = new myObject(); // instanceOfMyObjecct is an instance of myObject and has an observable // property called newProperty which is unique to this instance
-
\$\begingroup\$ I am most curious, what would you be modeling that requires a long inheritance chain ? I have yet to find something that requires more than 3 levels. \$\endgroup\$konijn– konijn2014年04月01日 23:32:47 +00:00Commented Apr 1, 2014 at 23:32
-
\$\begingroup\$ My current use case consists of a set of base classes used to generate a grid, I then create inherited classes for each instance of the grid, the user is then able to extend the classes for grid A without affecting the behavior of those classes in grid B (the goal is for a very customizable grid). The current paradigm would only involve 2-3 deep chains, but that will likely change as I continue adding features. For example the Row class will likely be extended by a sub-class AggRow, when a sub-class AggRow is created for an individual grid it will have an inheritance chain 3 deep. \$\endgroup\$cwohlman– cwohlman2014年04月02日 01:58:21 +00:00Commented Apr 2, 2014 at 1:58
-
\$\begingroup\$ I'd also like to plan for deeper inheritance if I can. \$\endgroup\$cwohlman– cwohlman2014年04月02日 02:03:21 +00:00Commented Apr 2, 2014 at 2:03
-
\$\begingroup\$ Good enough, can you update the snippet under 'Does this make for clear code', the snippet should include how you created the usefullBaseClass. \$\endgroup\$konijn– konijn2014年04月03日 13:05:13 +00:00Commented Apr 3, 2014 at 13:05
1 Answer 1
Interesting question,
Are there any performance hits ? I don't see problems, it would not be slower then building the objects in an old skool manner
Does this make for clear code? Your sample code would not run, you need to provide a sample that would actually run.
Furthermore:
- You should run you code through JsHint
- You are missing semicolons
- You are declaring some variables more than once in the same function
- Your code has
Ladder.prototype.attachComputed // doComputed
<- Pointless - Your code has
return duplicatePrototypes(prototypes);
<-duplicatePrototypes
is not provided dontInit
<-doNotInit
reads better, also there must be a better way to avoid initialization ;)doAttach
is a constructor, so definitely wrongly named.AttachedValue
mayhaps?- Anonymous functions are a pain in stacktraces, if you are going to create a custom OO approach, then I would avoid avoid anonymous functions like the plague.
// naming convention for extenders is _00Name where 00 is a priority
<- I do not believe this works,for (var prop in self) {
does not pre-sort property names, at least that is not guaranteed- The distinction between
doAttach
anddoComputed
is unclear, are the comments possibly out of date?// doAttach is called before config and should attach a computed or observable
<- It seems from the code that I would attach a computable throughdoComputed
? This:
//Note: extender.extender might simply return undefined self[prop] = extender.extender.apply(self, extender.args);
is better than
self[prop] = extender.extender.apply(self, extender.args); // || ('' + extender.extender); // if the function doesn't return a value perhaps it only performs operations? Leave a tatle tale behind?
- I guess
var self = this;
comes from your knockout background. If you are not using closures, then there is no good reason to use this. Do not drop newlines from
if
statements;if (ko && ko.isObservable(self[prop])) self[prop](ko.unwrap(val)); else self[prop] = val;
if (ko)
should be at the very start of your script, there is no point if knockout is not available
All in all, I think I would not use your library until it has matured a bit more.
Explore related questions
See similar questions with these tags.