1
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?

onlineracoon
  • 2,822
  • 4
  • 44
  • 65
  • 1
    [This question](http://stackoverflow.com/questions/1986896/what-is-the-difference-between-call-and-apply) on `call` and `apply` should help you. – Casey Foster Oct 08 '12 at 20:47
  • Why are you complicating things by creating an `extend` method? `userModel.create = function(data, callback) {...` – David Hellsing Oct 08 '12 at 20:50

4 Answers4

3

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
    };
  }
zetlen
  • 3,539
  • 22
  • 22
0
 this[key] = function() {
      fn.apply(self, arguments);
    };
Artur Udod
  • 3,968
  • 23
  • 53
0

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);
A. Matías Quezada
  • 1,824
  • 12
  • 30
  • That's the great thing about the `.extend` function as @onlineracoon implemented it. The pattern `function() { fn.apply(self,arguments) }` is analogous to `Function.prototype.bind`, so the method will *always* run in the context of the instance, even when passed as a reference! – zetlen Oct 09 '12 at 12:17
  • Yeah, but it has a disadvantage, @onlineracoon's extend function and Function.prototype.bind both create a new function who wraps the original function. It is not a problem usually, but when you are developing huge RIAs or a Javascript game you are duplicating callstack and function objects, so in cases like this is preferred to not have always-binded methods. – A. Matías Quezada Oct 09 '12 at 14:43
  • Oh yeah, it's not performant, but writing performant JS requires so much refactoring of inner loops that I generally don't apply performance concerns to other peoples' code unless they specifically ask for it. – zetlen Oct 09 '12 at 15:29
0

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.

David Hellsing
  • 97,234
  • 40
  • 163
  • 203