Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript automatic getter/setters (John Resig Book)

I'm reading "Pro Javascript Techniques" from John Resig, and I'm confused with an example. This is the 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;
  };
})(); }
}

// 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
});

// Just note that the name property does not exist, as it's private
// within the properties object
alert( user.name == null );

// However, we're able to access its value using the new getname()
// method, that was dynamically generated
alert( user.getname() == "Bob" );

// Finally, we can see that it's possible to set and get the age using
// the newly generated functions
user.setage( 22 );
alert( user.getage() == 22 );

Now running that on Firebug console (on FF3) throws that user.getname() is not a function. I tried doing this:

var other = User
other()
window.getname() --> this works!

And it worked!

Any idea why? thanks everybody!

PS: I strongly recommend this book.

EDIT:

doing:

var me = this;

seems to work a bit better, but when executing "getname()" it returns '44' (the second property)...

also I find it strange that it worked on the window object without modification...

and a third question, what's the difference between PEZ solution and the original? (he doesn't use an anonymous function)

Thanks to everyone for the feedback! +1

like image 704
Pablo Fernandez Avatar asked Dec 18 '08 12:12

Pablo Fernandez


3 Answers

I think it's best not to use the new keyword at all when working in JavaScript.

This is because if you then instantiate the object without using the new keyword (ex: var user = User()) by mistake, *very bad things will happen...*reason being that in the function (if instantiated without the new keyword), the this will refer to the global object, ie the window...

So therefore, I suggest a better way on how to use class-like objects.

Consider the following example :

var user = function (props) {
    var pObject = {};
    for (p in props) {
        (function (pc) {
            pObject['set' + pc] = function (v) {
                props[pc] = v;
                return pObject;
            }
            pObject['get' + pc] = function () {
                return props[pc];
            }
        })(p);
    }
    return pObject;
}

In the above example, I am creating a new object inside of the function, and then attaching getters and setters to this newly created object.

Finally, I am returning this newly created object. Note that the the this keyword is not used anywhere

Then, to 'instantiate' a user, I would do the following:

var john = user({name : 'Andreas', age : 21});
john.getname(); //returns 'Andreas'
john.setage(19).getage(); //returns 19

The best way to avoid falling into pitfalls is by not creating them in the first place...In the above example, I am avoiding the new keyword pitfall (as i said, not using the new keyword when it's supposed to be used will cause bad things to happen) by not using new at all.

like image 73
Andreas Grech Avatar answered Oct 06 '22 09:10

Andreas Grech


EDIT: now, adapting Jason's answer, it works:

We need to make a closure for the values. Here's one way:

function bindAccessors(o, property, value) {
  var _value = value;
  o["get" + property] = function() {
    return _value;
  };
  o["set" + property] = function(v) {
    _value = v;
  };
}

Then the User constructor looks like this:

function User( properties ) {
  for (var i in properties ) {
    bindAccessors(this, i, properties[i]);
  }
}
like image 4
PEZ Avatar answered Oct 06 '22 09:10

PEZ


you probably want something like this, which is more readable: (closures are easy to learn once you get some practice)

function User( properties ) {
  // helper function to create closures based on passed-in arguments:
  var bindGetterSetter = function(obj,p,properties)
  {
    obj["get"+p]=function() { return properties[p]; }
    obj["set"+p]=function(val) { properties[p]=val; return this; }
  };
  for (var p in properties)
    bindGetterSetter(this, p, properties);
}

I also added "return this;" so you can do:

u=new User({a: 1, b:77, c:48});
u.seta(3).setb(20).setc(400)
like image 3
Jason S Avatar answered Oct 06 '22 09:10

Jason S