10

Inside John Resig's book "Pro Javascript techniques" he describes a way of generating dynamic object methods with the below code:

// Create a new user object that accepts an object of properties
function User(properties) {
    // Iterate through the properties of the object, and make sure
    // that it's properly scoped (as discussed previously)
    for (var i in properties) {
        (function() {
            // Create a new getter for the property
            this["get" + i] = function() {
                return properties[i];
            };
            // Create a new setter for the property
            this["set" + i] = function(val) {
                properties[i] = val;
            };
        })();
    }
}

The problem is when I try instantiating the above object, the dynamic methods are being attached to the window object instead of the object instantiated. It seems like "this" is referring to the window.

// Create a new user object instance and pass in an object of
// properties to seed it with
var user = new User({
name: "Bob",
age: 44
});

alert( user.getname() );

Running the above code throws this error "user.getname is not a function".

What is the correct way of generating the dynamic functions for each object instantiated?

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
levi
  • 23,693
  • 18
  • 59
  • 73

5 Answers5

13

Is this code from the book? I have the book, but I haven't read through it.

It's an error in the book. Check the errata: http://www.apress.com/9781590597279

Inside the anonymous function, this is the global window.

You could make it work by adding .call(this, i) after it.

function User(properties) {
    // Iterate through the properties of the object, and make sure
    // that it's properly scoped (as discussed previously)
    for (var i in properties) {
        (function(i) {
            // Create a new getter for the property
            this["get" + i] = function() {
                return properties[i];
            };
            // Create a new setter for the property
            this["set" + i] = function(val) {
                properties[i] = val;
            };
        }).call(this, i);
    }
} 
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
gen_Eric
  • 223,194
  • 41
  • 299
  • 337
  • 2
    *I have the book, but I haven't read it though.* Then what **have** you been doing with it? Bookstop? Source of woody fiber? Wallpaper? – Jared Farrish Mar 04 '12 at 03:06
  • @JaredFarrish: It's being used to keep the books on my shelf symmetrical. No, but seriously, I just need some time to actually read it :-P – gen_Eric Mar 04 '12 at 03:07
  • 1
    It's from the book http://books.google.com/books?id=GgJN2CC_2s4C&pg=PA37&lpg=PA37&dq=pro+javascript+techniques+%22Iterate+through+the+properties+of+the+object,+and+make+sure%22&source=bl&ots=7x09hU3aSL&sig=cCyo8_CuwNpXhi83LZEdcJGkCJE&hl=en&sa=X&ei=1ttST8qKCsTh0QH7qYDtDQ&ved=0CEcQ6AEwBQ#v=onepage&q&f=false – qwertymk Mar 04 '12 at 03:07
  • Pass it down when you're finished chewing. `;O` – Jared Farrish Mar 04 '12 at 03:08
  • What's the purpose of the `val` parameter passed to the anon function? It doesn't look like it's being used... – Peter Olson Mar 04 '12 at 03:10
  • @PeterOlson: That would be a typo :-P – gen_Eric Mar 04 '12 at 03:12
4

The this in the inner self-executing function is not the same as the this in the outer User function. As you noticed, it refers to the global window.

The problem is fixed if you slightly modify the code by adding a variable that refers to the outer this.

function User(properties) {
  var self = this;
  for (var i in properties) { 
    (function() { 
      self["get" + i] = function() { /* ... */ }; 
      self["set" + i] = function() { /* ... */ }; 
    })();
  }
}

That said, I'm not sure why the anonymous self-executing function is even needed here, so you have the simpler option of just leaving it out entirely, like this:

function User(properties) {
  for (var i in properties) { 
      this["get" + i] = function() { /* ... */ }; 
      this["set" + i] = function() { /* ... */ }; 
  }
}
Peter Olson
  • 139,199
  • 49
  • 202
  • 242
1

Here is how to do it. You need to save the context into another variable. The other option is not to do this inner function that you are doing in the for loop.

// Create a new user object that accepts an object of properties
function User( properties ) {
   // Iterate through the properties of the object, and make sure
   // that it's properly scoped (as discussed previously)
   var that = this;
   for ( var i in properties ) { (function(){
       // Create a new getter for the property
       that[ "get" + i ] = function() {
          return properties[i];
       };
       // Create a new setter for the property
       that[ "set" + i ] = function(val) {
           properties[i] = val;
       };
    })(); }
}

Option 2:

// Create a new user object that accepts an object of properties
function User( properties ) {
   // Iterate through the properties of the object, and make sure
   // that it's properly scoped (as discussed previously)
   for ( var i in properties ) {
       // Create a new getter for the property
       this[ "get" + i ] = function() {
          return properties[i];
       };
       // Create a new setter for the property
       this[ "set" + i ] = function(val) {
           properties[i] = val;
       };
    }
}
Nikola Borisov
  • 348
  • 1
  • 10
  • Both of these options do not work as they use the captured version of `i` which changes as the loop is being executed and leaves `i` as the last property name. Part of John's example was how to avoid this problem. Option 1 can easily be fixed, however, by passing `i` as a parameter. To fix Option 2 you would turn it into a fixed version of Option 1. – chuckj Mar 04 '12 at 03:36
1

You can always force another this for any function call, using the apply method.

(function() {
    // Create a new getter for the property
    this["get" + i] = function() {
        return properties[i];
    };
    // Create a new setter for the property
    this["set" + i] = function(val) {
        properties[i] = val;
    };
}).apply(this);
Endel Dreyer
  • 1,644
  • 18
  • 27
0

You can also make a new function that has uses a certain this with the .bind method.

function getaloadofthis() {return this}

If you do getaloadofthis() it just returns window but if you do getaloadofthis.bind(3)() it returns 3.

So you could also have

const getaloadofthis3 = getaloadofthis.bind(3)
getaloadofthis3() // 3

This also works with anonymous functions

(function() {return this}).bind(3)() // 3
Quinten C
  • 660
  • 1
  • 8
  • 18