4

I am following a tutorial to create getter and setter in Javascript, I have the code like this:

// Create a new User object that accept an object of properties
function User(properties) {
    // Iterate through the properties of the object, and make
    // sure it's properly scoped
    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: 28
});

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

// However, we are able to access its value using the new getname()
// method, that was dynamically generated
console.log(user.getname());

However, console shows error saying user does not have method getname. The code is trying to dynamically generate getter and setter method, it looks ok to me. Any thoughts?

10
  • Your in-loop function is losing the this, do a var t = this; outside loop and refer to t inside. Commented Jul 20, 2013 at 17:33
  • 2
    You could also use es5 getters/setters. Commented Jul 20, 2013 at 17:34
  • Then you need to know that i as you use it in the getter/setter will always have the same value (i.e., the last value of the loop) for all getters/setters the code creates. Commented Jul 20, 2013 at 17:35
  • 1
    Why do you have an anonymous function in your for loop? What purpose does it serve? Are you populating from a web service? Commented Jul 20, 2013 at 17:36
  • @tjameson It's there for a reason. If it wasn't there than i would always be the last value. However he is using it wrong as your suppose to pass i as an argument to the function. Commented Jul 20, 2013 at 17:40

3 Answers 3

15

The other answers are correct in that you need to pass i into your anonymous function, but you could also do this with ES5 Getters and Setters:

// Create a new User object that accept an object of properties
function User(properties) {
    var self = this; // make sure we can access this inside our anon function
    for (var i in properties) {
        (function(i) {
            Object.defineProperty(self, i, {
                // Create a new getter for the property
                get: function () {
                    return properties[i];
                },
                // Create a new setter for the property
                set: function (val) {
                    properties[i] = val;
                }
            })
        })(i);
    }
}

The benefit of using ES5 getters and setters is that now you can do this:

var user = new User({name: 'Bob'});
user.name; // Bob
user.name = 'Dan';
user.name; // Dan

Since they're functions, they modify the passed in properties, not just the object itself. You don't have to use getN or setN anymore, you can just use .N, which makes using it look more like accessing properties on an object.

This approach, however, isn't universally portable (requires IE 9+).

Here's what I'd probably do in practice though:

function User(properties) {
    Object.keys(properties).forEach(function (prop) {
        Object.defineProperty(this, prop, {
            // Create a new getter for the property
            get: function () {
                return properties[prop];
            },
            // Create a new setter for the property
            set: function (val) {
                properties[prop] = val;
            }
        })
    }, this);
}

The above gets rid of your for loop. You're already using an anonymous function, so might as well get the most of it.

Sign up to request clarification or add additional context in comments.

4 Comments

A nice side-effect is that you can't accidentally assign over the top: user.setname = 'Dan'. Not really a big issue, but I've seen people accidentally do that and wonder why their their code broke.
I've added what I consider to be better style. for .. in can cause weird problems (probably not in this case though), and I think Array.forEach is mor elegant anyway.
@tjameson ES5 getters and setters are great, but if you're doing nothing more than mirroring, I don't see the point over just using an Object.
@PaulS. This can be useful if you want to use properties wholesale later (e.g. upload to a server) without allowing extra properties to be attached. Maybe the OP has other properties on User that only make sense in the web app and not in a potentially later POST to the server. But I agree, the OP said nothing about why he/she wanted this.
2

Probably a closure issue:

function User(properties) {
    // Iterate through the properties of the object, and make
    // sure it's properly scoped
    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));
    }
}

Also, as @Paul pointed out, this was actually referring to the function which was contained in the for loop. Not the User function. I fixed that by using a call and assigning the User this to the function (no need for extra variable).

5 Comments

Thanks. This looks quite elegant. But I don't understand why using call works? What's the essence of 'call'?
call allows you to define this. .call(this, i) the this in the call function is referring to the this of the user function. That is then passed on to the function. It is equal to using something like .bind(this)(i). Only difference is that call triggers the function and allows you to set the arguments within the function.
It's an issue with closures. I am the worst with explaining closures. If you wouldn't pass i to call to use it in the function. i would always equal the last property in the Object.
I thought the anonymous function already handled the 'last i' problem.
Well no, it doesn't fix it unless you pass i as an argument. Without passing argument: jsfiddle.net/shawn31313/zU7cE. With passing argument: jsfiddle.net/shawn31313/zU7cE/1
1

Your in-loop function is losing the this, do a var t = this; outside loop and refer to t inside. Also, pass i into your function.

function User(properties) {
    var t = this, i;
    for (i in properties) (function (i) {
        t['get' + i] = function () { return properties[i]; };
        t['set' + i] = function (val) { properties[i] = val; };
    }(i));
}

1 Comment

This is a great solution. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.