Question

I have this constructor object:

function Bindable(obj) {
    var prop;

    for (prop in obj) {
        this.__defineGetter__(prop, function () {
            return obj[prop];
        });

        this.__defineSetter__(prop, function (val) {
            obj[prop] = val;
        });
    }
}

Which is being called like so:

var model = new Bindable({
    name: 'Dave',
    level: 10,
    strength: 5
});

If I console.log(model), the output is:

Bindable { name=5, level=5, strength=5 }

Why is the same value getting assigned to each of the properties in the constructor?

Was it helpful?

Solution

You are creating a closure that gets evaluated when the getter/setter functions execute, not when they are defined. At the time they execute, prop has the last value it was assigned during execution of the loop. You need to avoid creating a closure on prop. One way is with an IIFE. Instead of using this argument:

function () {
    return obj[prop];
}

use

(function(p) {
    return function() {
        return obj[p];
    };
}(prop));

OTHER TIPS

As an alternative, this should work for you.

Same principle as explained by @Ted Hopp

But now we have a reusable function createGetterSetter which uses the ECMA5 method Object.defineProperty for defining the getters and setters, as pointed out by @Benjamin Gruenbaum.

It is also using the ECMA5 Object.keys instead of for..in that you used.

I think this is a little tidier than the solution Ted suggested (everyone has their preference).

The only part that I am unsure about room for improvement is var self = this;

Javascript

/*jslint maxerr: 50, indent: 4, browser: true */
/*global console */

(function () {
    "use strict";

    function createGetterSetter(object, map, prop) {
        Object.defineProperty(object, prop, {
            get: function () {
                return map[prop];
            },
            set: function (val) {
                map[prop] = val;
            }
        });
    }

    function Bindable(obj) {
        var self = this;

        Object.keys(obj).forEach(function (prop) {
            createGetterSetter(self, obj, prop);
        });
    }

    var model = new Bindable({
        name: 'Dave',
        level: 10,
        strength: 5
    });

    console.log(model.name, model.level, model.strength);
}());

Output

Dave 10 5

On jsfiddle

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top