var Model = function(client, collection) {
this.client = client;
this.collection = collection;
};
Model.prototype = {
constructor: Model,
getClient: function(callback) {
this.client.open(callback);
},
getCollection: function(callback) {
var self = this;
this.getClient(function(error, client) {
client.collection(self.collection, callback);
});
},
extend: function(key, fn) {
var self = this;
this[key] = function() {
fn.call(self); // A
};
}
};
What I want to achieve is that I can "extend" the functionality of the model.
var userModel = new Model(client, 'users');
userModel.extend('create', function(data, callback) {
this.getCollection(function(error, collection) {
collection.insert(data, { safe: true }, function(error, doc) {
callback.call(doc);
});
});
});
userModel.create({ fullName: 'Thomas Anderson' }, function() {
console.log(this); // { _id: '123456789012345678901234', fullName: 'Thomas Anderson' }
});
Someway at A, I have to do parameter passing, the parameter count of the custom "create" function (data and callback) are variable.
Is this possible and if so how?
Yep! You want to use the .apply
method over the .call
method. Both apply context, but the .apply
method takes an array for arguments--or an arguments collection itself!
extend: function(key, fn) {
var self = this;
this[key] = function() {
fn.apply(self,arguments); // the special arguments variable is array-like
};
}
this[key] = function() {
fn.apply(self, arguments);
};
Just one more thing after @zetlen comment, the scope (this) of a function is what is behind the last dot of the function invocation, in this case:
userModel.create();
The function's "this" variable will be "userModel".
So as long as you call it with "modelname.functionName()" you don't need to pass self:
extend: function(key, f) {
this[key] = f;
}
(or just)
userModel.someMethod = function() { ... };
Then when you call modelkey you will pass the arguments directly to the function and you will have the scope you want.
var userModel = new Model(...);
userModel.extend('hi', function(arg) {
this === userModel;
arg === 42;
});
userModel.hi(42);
Look at this fiddle to see it: http://jsfiddle.net/NcLQB/1/
But please, keep in mind than if you take out the function of the model it will not have the scope no more, in the case you do this you better keep doing it like your snippet.
var funct = userModel.hi;
funct(); // this === global!!!
// OR
// When the event is fired the scope isn't userModel
otherThing.on('someEvent', userModel.hi);
Unless you have any hidden reasons for creating an extend
method in the prototype and enforcing a scope, you can just assign the method to the created instance instead:
var userModel = new Model(client, 'users');
userModel.create = function(data, callback) {
this.getCollection(function(error, collection) {
collection.insert(data, { safe: true }, function(error, doc) {
callback.call(doc);
});
});
});
It will be faster, cleaner and you don’t have to worry about passing arguments via another function.
Enforcing a scope (or context) by encapsulating it is not always a good thing, because you are restricting the dynamics of the language.