Closed demisx closed 9 years ago
I wouldn't think in terms of a rule of thumb here. What's more important is whether you can let JS help you via the implicit this
. Think of this
as a special variable, i.e. fn.apply(specialArg, regularArgs)
. The only magic is that evaluating obj.fn()
will implicitly call fn
on obj
.
When you're assigning something to the prototype, it is indeed shared by all instances. Individual instances can also overwrite that property for just themselves without affecting other instances. I would add the concept of "private" to the mix since the way you're describing isn't really correct anyway.
An easy example of a case where you want a prototype method is validation. Given Model.prototype.validate
, the validate
function will receive the model
instance it was called from (i.e. model.validate()
as this
. Then you can use the model properties in validate
.
A comparable class method would be:
Model.validate = function (model) {
doValidation(model.attributes);
};
In that case you're wasting code. By using the implicit this
you save the trouble of passing model
around.
Class methods (static in ES6 parlance) tend to be best reserved for methods that don't use instance data. If you wanted to implement an Active Record interface with methods like create
and find
, you'd do that with static methods. Bookshelf generally favors the approach of defining instances and then it uses the attributes of the instance to generate queries, as in new Model({id: 1}).fetch()
instead of Model.fetchById(1)
. There are a lot of static helpers though that do the later by using the former under the hood.
The other thing to keep in mind about static methods is that you may still have a use case for this
:
class Model {
static where (params) {
return query(params);
}
static all () {
return this.where();
}
}
class User extends Model {
static where (params) {
params = params || {};
params.active = true;
return super.where(params);
}
}
In that example, User
defines a static method where
that overrides Model.where
. But because Model.all
uses the dynamic this
instead of explicitly referencing Model.all
, our inheritance/override pattern is more powerful. User.all
will only get active users.
Excellent answer! Coming from a Java class-based inheritance to protypical-inheritance does make some things confusing to me cause I guess I expect JS to work sometimes as Java would. It's getting better though. :) Thank you very much for your time explaining in such detail. It really helps.
P.S. Bummer I have to close this, since many others could find this answer useful too. Maybe it could be a blog post at some point?
It can! Too little time, too many post ideas. Got a few flights coming up. Might try to bang out one each.
Hi Ben, When creating a new bookshelf model, we have an option of adding properties either to the model prototype or to the constructor function as class properties, i.e.:
Do you have a rule of thumb to share, when mode properties should be added as proto or as class? My understanding, that prototype properties are shared between all model instances, but without access to private vars and class properties are created for each object separately and have access to private vars. I can't seem to make up a rule in my head, so it's easy to remember.
Thank you.