views:

92

answers:

6

I'm trying to create a global object with a series of 'slave' properties that are derived from the value of one 'master' property in the same object - something like:

var x = 5;
var myObj = {
    master : 17,
    slave1 : this.master + x,
    slave2 : (this.master / 2.2) + 1,
    slave3 : Math.floor(this.slave2)
    //etc.
};

I realize this is horribly wrong as is, but the concept is there. What I'd like to do is have all these 'slave' properties updated as myObj.master is updated. What's the cleanest way to do this? Does each slave value need to be explicitly set by some event handler that fires when myObj.master is changed...?

+2  A: 

You would probably be best off using functions.

var x = 5;
var myObj = {
    master : 17,
    slave1 : function() { return this.master + x },
    slave2 : function() { return (this.master / 2.2) + 1 },
    slave3 : function() { return Math.floor(this.slave2()); }
    //etc.
};

var example = myObj.slave3();
David Dorward
+2  A: 

David's answer is one option, but if you want to cache the results instead of recalculating them every time, do it the other way round:

var x = 5;
var myObj = {
    setMaster: function(val) {
        this.master = val;
        this.slave1 = this.master + x;
        // ...
    }
};

myObj.setMaster(17);
Matti Virkkunen
if you're going to take this approach, i'd prefer to have the master property as a private member, so it can't be set directly causing the slave properties to be wrong. you'd have to replace the literal object notation with a constructor function.
lincolnk
A: 

Another option can be to use a constructor function to build your object, e.g:

var x = 5;

function MyObj(master) {
  this.master = master;
  this.slave1 = this.master + x,
  this.slave2 = (this.master / 2.2) + 1,
  this.slave3 = Math.floor(this.slave2)
}

var myObj = new MyObj(17);

In the above example I use a master argument and a reference to the x global property, if you don't have x available in that scope, you could provide also an argument for it.

You can also build your object in several steps:

var myObj = {
    master : 17
}, x = 5;

myObj.slave1 = myObj.master + x;
myObj.slave2 = (myObj.master / 2.2) + 1;
myObj.slave3 = Math.floor(myObj.slave2);
CMS
+4  A: 

If you are not targeting Internet Explorer users, try using getters and setters.

var x = 5;
function myObj()
{
    this.master = 17;
    this.getSlave1 = function() {return this.master + x;},
    this.getSlave2 = function() {return (this.master / 2.2) + 1;},
    this.getSlave3 = function() {return Math.floor(this.getSlave2());}
}

myObj.prototype = {
    get slave1() {
        return this.getSlave1();
    },
    get slave2() {
        return this.getSlave2();
    },
    get slave3() {
        return this.getSlave3();
    }
};   

In action:

window.onload = function()
{
    o = new myObj();
    document.write("Master: "+o.master+"</br>");
    document.write("Slave 1: "+o.slave1+"</br>");
    document.write("Slave 2: "+o.slave2+"</br>");
    document.write("Slave 3: "+o.slave3+"</br></br>");
    o.master = 42;
    document.write("Master: "+o.master+"</br>");
    document.write("Slave 1: "+o.slave1+"</br>");
    document.write("Slave 2: "+o.slave2+"</br>");
    document.write("Slave 3: "+o.slave3+"</br>");
}

produces the output:

Master: 17
Slave 1: 22
Slave 2: 8.727272727272727
Slave 3: 8

Master: 42
Slave 1: 47
Slave 2: 20.09090909090909
Slave 3: 20

This code will never work in any version of Internet Explorer, past, present or future. However, it the slave values can still be accessed using the getSlaveX() functions.

Andrew Dunn
yes, getters is exactly what OP wants
stereofrog
I feel that this is error prone. You have the formulas in two places and the slaves can be changed without changing master.
mangoDrunk
@mangoDrunk: Good point, I updated the code to only use getters to retrieve the slave values, making them readonly, as well as making it so the formulas are only input once.
Andrew Dunn
@Andrew Dunn: Looks good. I didn't know about the get/set, it's pretty sweet.
mangoDrunk
Andrew, getters would probably work in IE 9, as they've actually written a decent engine this time around.
Delan Azabani
IE8 uses Object.defineProperty for DOM objects only and in IE9 will most likely allow its use for javascript objects. Since the above code doesn't use the Object.defineProperty method, it most likely won't work.
Andrew Dunn
@Andrew, it indeed works on IE9, the `PropertyAssignment` in the form of `get PropertyName () { FunctionBody }` is a syntax addition to the grammar of the [Object Initializer](http://ecma262-5.com/ELS5_HTML.htm#Section_11.1.5) on ECMAScript 5.
CMS
A: 

You don't want to expose the slaves and allow them to be set since that would erase their link to master. So they should be private.

var x = 5;
var MyObject = function() {
    var master = 17;
    this.setMaster = function(newMaster) {
        master = newMaster;
    };
    this.getMaster = function() {
        return master;
    };
    var slaves = {
        slave1 : function() { return master + x },
        slave2 : function() { return (master / 2.2) + 1 },
        slave3 : function() { return Math.floor(this.slave2()) }
    };
    this.getSlave = function(slave) {
        return slaves[slave]();
    };
};

var o = new MyObject();
o.getSlave("slave1"); // 22
o.getSlave("slave3"); // 8
o.setMaster(3);
o.getSlave("slave1"); // 8
o.getSlave("slave3"); // 2
mangoDrunk
A: 

You could simulate getters using valueOf and toString, but it's a little unwieldy.

function makeValueOf(fn) {
    return {
        valueOf: fn,
        toString: fn
    };
}

function makeObj(master, x) {
    var self = {};

    self.master = master;
    self.slave1 = makeValueOf(function () { return self.master + x; });
    self.slave2 = makeValueOf(function () { return self.master / 2.2 + 1; });
    self.slave3 = makeValueOf(function () { return Math.floor(self.slave2); });

    return self;
}

var myObj = makeObj(6, 2);
alert(myObj.slave1 + 5); // 13
Casey Hope