0

OK guys - been banging my head on this one. I feel this is a "pointer" issue while using an array of classes built from the Revealing Prototype Pattern. Also, I'm using knockoutjs for my binding.

Issue: I commonly have very involved pages on my site. As a result, I tend to have classes that contain arrays of other classes. I use the Revealing Prototype Pattern all the time and usually have a "self" variable that gets pointed to "this" in the init function so that other functions in that class that are called by event handlers will have a pointer to this (and it doesn't fail - it seems to know what "self" is).

BUT....when I have arrays of a class(model) it doesn't work correctly....my MailModel class (my viewmodel) has an array of People classes..."self" in the People class gets pointed to "this", but when other functions of the prototype call "self", it seems to be pointing to the LAST "person" item in the array?? - Am I somehow exposing "self" as a shared function on all the classes it instantiates....and somehow pointing to that last instance of "this"??

I tried to do a simple mock-up of a viewModel containing an array of "Person" classes. PLEASE someone help me figure this out. I've tried different ways of using this "this" pointer on the "Person" class, but to no avail.

Check it out!

http://jsfiddle.net/N8vxr/.

var $results = $('#results');

var Person = function(data) {
  this.id = ko.observable();
  this.name = ko.observable();
  this.init(data);
}

Person.prototype = function(){
    var self,
        init = function(data){
            self = this;

            self.id(data.id || 0)
                .name(data.name)

        },
       sayHi = function(){
            $results.find('.wrong').text(self.name()).end()
                    .find('.correct').text(this.name()).end()
                    .show();
        };
    return {
        init: init,
        sayHi: sayHi
    }
}();

var MainModel = function(people){
    this.people = ko.observableArray([]);
    this.init(people);
}

MainModel.prototype = function(){
    var self,
        init = function(people){
            self = this;

            _.each(people, function(person){
                self.people.push(new Person(person));
            }); 

            /* do lots of other stuff */
        };
    return {
        init: init
    }
}();

var people = [
    {id: 1, name: 'Bill Smith'},
    {id: 2, name: 'John Doe'},
    {id: 3, name: 'Suzy Chapstick'},
];

var vm = new MainModel(people);

 ko.applyBindings(vm);
UPDATE:

I see the comment wondering why I would not just use "this" in my call - In my case, I have other things calling these prototype functions that are NOT the person instance (so "this" doesn't work) - I have updated my jsFiddle to show the Person prototype with a totalNums KnockoutJS computed function for totals - if I put "this" in that function, it fails since "this" is the "Window" object - and as before, you can see that using "self" just refers to the last person in the array (notice everyones "totalNums" is 80 (Suzy Chapstic)

If I move that "totalNums" up into the constructor, it is fine, but I want my functions down in the prototype. So......how can I get a reference to the "this" instance inside of that computed function on the prototype?

Here is the updated fiddle with this KnockoutJS computed function:

http://jsfiddle.net/Sj25M/

Jeff
  • 865
  • 1
  • 7
  • 7
  • Hi, I am relative newbie with javascript, currently reading up on design patterns. Could it be that calling a prototype method from the constructor (init in your case) is causing the problem? I haven't seen any examples yet where this is done. Have you tried calling the init after creating the instance (ie taking the call out of the constructor)? This comment comes with a newbie disclaimer :) – devboell Feb 14 '14 at 11:02
  • Hey - actually - that isn't the issue - like "ebohlman" mentioned below - since that "self" variable is in the "prototype", it is shared - so, that "last one in" in the array was what that "computed" function was pointing to - so....to get around this, like when hooking up jquery events in a prototype, I need to define that function "inside" the init while I still have a pointer to "this" - I have update that last fiddle of mine to show it correctly working - http://jsfiddle.net/Sj25M/1/ – Jeff Feb 14 '14 at 14:48
  • ...addendum to my last comment - you will see in Walin's post on using Revealing Prototype Pattern (at the very bottom of the post) - he uses an "init" function on the prototype when you have to set up jquery events tied to that instance - very useful - I was just using those ko.computed functions incorrectly - http://blog.pluralsight.com/revealing-prototype-pattern-structuring-javascript-code-part-iv – Jeff Feb 14 '14 at 14:53
  • ye - but in that article it's not clear at what point the call to init is made. I was not sure if calling init during instantiation (while the constructor is being executed) was safe. I mean, at that time the object is not yet fully formed, so maybe 'this' wasn't defined yet either. Anyway, it's a moot point now, as you said the problem was due to something else. Thanks. – devboell Feb 15 '14 at 07:35

1 Answers1

0

self is indeed a closure variable of Person.prototype.init and consequently it's shared by all instances; it one instance changes it, all the others see the change. That's the way closures work; what gets "trapped" by a closure is a reference to the variable, not the value of the variable at the time of instantiation.

I'm not sure why you need it in the first place: whenever any method in Person.prototype is called, this will be pointing to the correct instance. If one of the methods needs to invoke an anonymous function that needs the instance as a closure variable, it can create a local variable var self=this and use it.

ebohlman
  • 14,795
  • 5
  • 33
  • 35
  • Please see my UPDATE above and new jsFiddle link for my reason for needing to use something other than "this" in that KnockoutJS computed function on the prototype - – Jeff Feb 09 '14 at 04:16
  • How would one store the instance of this, such that a click bindingHandler for example would retain "this" as the instance? I typically have been forced to resort to using myFunction.bind($root, whatever) in my markup which I don't like to do. However on the normal click bindingHandler "this" has a completely different scope when a function is called from the bindingHandler. – Brandon Apr 28 '15 at 04:55
  • After further research I am starting to think that the answer lays somewhere in the use of the .bind() function. But I would still love to hear some other opinions. – Brandon Apr 28 '15 at 05:09