0

I want to create an object that has modified versions of all of the methods in a source object, but I'm having trouble using for...in.

If this is my source object:

var raw = {};
raw.add = function(a,b){return a + b;}
raw.sub = function(a,b){return a - b;}
raw.neg = function(a){return -a;}
raw.sqrt = function(a){return Math.sqrt(a);}

It works if I recreate the list of properties in an array of strings:

var mod2 = Object.create(raw);
var proplist = ["add", "sub", "neg", "sqrt"];
proplist.forEach(function(prop){

    mod2[prop] = function(){
        var arglist = [].slice.apply(arguments);
        var out = [];
        if(arglist.length == 1){
            [].concat(arglist[0]).forEach(function(d){ out.push(raw[prop](d)); });
        }
        else if(arglist.length == 2){
            [].concat(arglist[0]).forEach(function(d1){
                [].concat(arglist[1]).forEach(function(d2){
                    out.push(raw[prop](d1,d2));
                })
            });
        }
        return out;
    }
});

But my attempt to use for..in doesn't work, all of the methods in the new object will do "sqrt":

var modified = Object.create(raw);
for(prop in raw){

    modified[prop] = function(){
        var arglist = [].slice.apply(arguments);
        var out = [];
        if(arglist.length == 1){
            [].concat(arglist[0]).forEach(function(d){ out.push(raw[prop](d)); });
        }
        else if(arglist.length == 2){
            [].concat(arglist[0]).forEach(function(d1){
                [].concat(arglist[1]).forEach(function(d2){
                    out.push(raw[prop](d1,d2));
                })
            });
        }
        return out;
    }
}

What is the best way to iterate through the methods automatically?

goutos19
  • 13
  • 2
  • Your `for (prop in raw)` for loop is probably working fine (looks OK to me). What are you trying to accomplish in the function. That looks way more complicated than I'm imagining that it needs to be, but I don't know what you're trying to accomplish with it. – jfriend00 Jun 02 '14 at 05:11
  • In this example I'm trying to get versions of the raw methods that handle arrays instead of scalar arguments. In the modified object the single-argument methods perform their operation on each element in a single array argument, and the two-argument methods perform their operation on all possible combinations of elements (a,b) from their arguments (arrayA, arrayB) and return an array of all those results. I would be interested to know about better ways to achieve this. – goutos19 Jun 02 '14 at 14:41
  • `Object.keys(raw).forEach(function() {…})` will do it. – Bergi Jun 03 '14 at 01:18
  • @Bergi - so in your mind every single question who's solution is ultimately to create a closure is a duplicate - no matter what the rest of the content of the question is? No matter how many other issues are discussed in the answers and comments? – jfriend00 Jun 03 '14 at 03:55
  • at least he is totally dedicated to closure in every sense – goutos19 Jun 03 '14 at 04:12
  • @jfriend00: Hmm, most of them are, especially when the OP already realized that the last value from the loop is used when being invoked later. To me, this seemed to be the only problem in this question. If you disagree, feel free to reopen the question. – Bergi Jun 03 '14 at 04:12

3 Answers3

0
<script>
    var raw = {};
    raw.add = function () { console.log('add default method'); }
    raw.sub = function () { console.log('sub default method'); }
    raw.neg = function () { console.log('neg default method'); }
    raw.sqrt = function () { console.log('sqrt default method'); }
    console.log('*****************');
    console.log('before modifying');
    console.log('*****************');
    raw.add();
    raw.sub();
    raw.neg();
    raw.sqrt();
    var proplist = ["add", "sub", "neg", "sqrt"];
    console.log('*****************');
    console.log('after modifying');
    console.log('*****************');
    console.log('');
    var modified = Object.create(raw);
    for (prop in proplist) {
        if (prop == 0)
            console.log('rewriting methods and calling methods inside loop................');
        modified[proplist[prop]] = function () { console.log(proplist[prop] + ' method  modified, ' + proplist.length + ' argument passed') }
        modified[proplist[prop]]();
    }
    console.log('');
    console.log('trying call methods after loop is done................');
    modified.add();
    modified.sub();
    modified.neg();
    modified.sqrt();
    console.log('...it is becaouse "prop" variable in loop holding last count number ' + prop);
</script>
0

thanks to arnold.NET.JS's response clarifying the problem, I see that closure is one way to do it:

var raw = {};
raw.add = function(a,b){return a + b;}
raw.sub = function(a,b){return a - b;}
raw.neg = function(a){return -a;}
raw.sqrt = function(a){return Math.sqrt(a);}

var mod = Object.create(raw);
for(prop in raw){

    mod[prop] = (function(){

        var propname = prop;
        function f(){
            var arglist = [].slice.apply(arguments);
            var out = [];
            if(arglist.length == 1){
                [].concat(arglist[0]).forEach(function(d){ out.push(raw[propname](d)); });
            }
            else if(arglist.length == 2){
                [].concat(arglist[0]).forEach(function(d1){
                    [].concat(arglist[1]).forEach(function(d2){
                        out.push(raw[propname](d1,d2));
                    })
                });
            }
            return out;
        }
        return f;
    })();
}
goutos19
  • 13
  • 2
0

The issue with your second implementation is that you are using prop in your new method (which will be called sometime later), but the for loop that creates prop has already run to completion by the time that method is called sometime later so prop is not the right value any more (it will always be the last property). I fixed that in my implementation by capturing prop in an IIFE (immediately invoked function expression) so it would be frozen separately for each pass through the for loop. Your first implementation doesn't have that problem because you're using .forEach() on the array of properties which uses a callback function which captures the value of prop for you automatically into a closure.


So here's the result with these changes to your implementation:

  1. Add an IIFE to freeze the value of prop for use in the new methods.
  2. Add an extra check to make sure the methods we're copying are not inherited and are functions.
  3. Initialized raw to a plain object as I don't see any reason to use Object.create() here.

The code:

var raw = {};
raw.add = function(a,b){return a + b;}
raw.sub = function(a,b){return a - b;}
raw.neg = function(a){return -a;}
raw.sqrt = function(a){return Math.sqrt(a);}

var modified = {};
for (prop in raw) {
    if (raw.hasOwnProperty(prop) && typeof raw[prop] === "function") {
        (function (prop) {
            modified[prop] = function () {
                var arglist = [].slice.apply(arguments);
                var out = [];
                if (arglist.length == 1) {
                    [].concat(arglist[0]).forEach(function (d) {
                            out.push(raw[prop](d));
                    });
                } else if (arglist.length == 2) {
                    [].concat(arglist[0]).forEach(function (d1) {
                        [].concat(arglist[1]).forEach(function (d2) {
                            out.push(raw[prop](d1, d2));
                        })
                    });
                }
                return out;
            }
        })(prop);
    }
}

Working demo: http://jsfiddle.net/jfriend00/5LcLh/

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • great, thanks. I was using [].concat() so that any non-array argument would be converted into an array. In your jsfiddle, the statement `modified.add(1, [4,3])` does not work unless I replace those concat statements. What is the correct way to do that? – goutos19 Jun 03 '14 at 03:37
  • @goutos19 - I didn't know that was a legal form of arguments (one array, one non-array). You didn't document what form the arguments could take. That would have helped us work on simpler ways to do the main body of your code (which I still think can be simplified), but I stayed away from major changes because I wasn't sure what your input spec was. OK, I guess you can put the concat stuff back. I'm curious, what are you using a function for that returns the results for all possible combinations of arguments? – jfriend00 Jun 03 '14 at 03:52
  • @goutos19 - I put the concat stuff back and added your test case to my jsFiddle. – jfriend00 Jun 03 '14 at 04:01
  • This is my first time asking a question here, and I'll keep this in mind and be more specific next time. I didn't mean to knock your answer which certainly does satisfy everything I specified - thank you. I want to handle array args like this so that sqrt can return both positive and negative possible solutions, and have them propagate in a calculation. – goutos19 Jun 03 '14 at 04:06