Solution two

Here’s the code we are studying. The first task is make a loop that does the assignments.

var copy_attributes = function(tgt, src){

    tgt.aaa = src.get_aaa();
    tgt.bbb = src.get_bbb();
    tgt.ccc = src.get_ccc();
    tgt.ddd = src.get_ddd();
    tgt.eee = src.get_eee();
    tgt.fff = src.get_fff();
    tgt.ggg = src.get_ggg();
    tgt.hhh = src.get_hhh();
};

Making a loop

As previously hinted, we use item access rather than attribute access. The problem with attribute access is that the name of the key is hard-coded and we want it to vary during the loop.

Here’s the solution. There’s a bit of overhead in setting up the loop, but the body of the loop is straightforward.

var copy_attributes = function(tgt, src){

    var keys = ['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff', 'ggg', 'hhh'];
    var i;
    var key;

    for(i=0; i< keys.length; i++){
        tgt[key] = src['get_' + key]();
    }
};

Factory function

The second task is to write a factory function that takes the list of keys as a parameter.

This is a common refactoring pattern, and so is worth learning well. First we give the code, and then the explanation.

var copy_attributes_factory = function(keys){

    return function(src, tgt){
        var i;
        var key;

        for(i=0; i< keys.length; i++){
            tgt[key] = src['get_' + key]();
        };
    };
};

Here’s what we’ve done. We created a wrapper function, with keys as its only parameter. This will be our factory function. We’ve placed the original function into the body of the wrapper, and return it (as the value of the wrapper function). Finally, we clean up by removing the keys constant from the inner function.

There, it’s done! When the factory function executes it returns an instance of the inner function for which keys is the argument we supplied to the factory.

A prototype class

The third task is to write a Fields class that has a copy_attributes method.

Here’s a function prototype based solution. It relies on the user of the class supplying the new operator to create the new instance (and chaos results if the user forgets).

var Fields = function(keys){
    this.keys = keys;
};

Fields.prototype = {
    copy_attributes : function(src, tgt){

        var keys = this.keys;
        var i;

        for(i=0; i< keys.length; i++){
            tgt[key] = src['get_' + key]();
        };
    }
};

A minor point is the trailing sequence of right curly braces. Two are followed by a semicolon, but the middle one is not. Why? What happens if we put a semicolon there?

A SimpleClass solution

Here’s another solution to the third task, which does not require the user to supply a new operator when creating instances. Instead it uses a factory function, which we call Class , that creates an instance constructor function from a prototype object.

// Rhino $ js -f library.js -f solution-2-d.js -

var fields = {};

fields.__init__ = function(keys){
    this.keys = keys;
};

fields.copy_attributes = function(src, tgt){

    var keys = this.keys;
    var key;
    var i;

    for(i=0; i< keys.length; i++){
        key = keys[i];
        tgt[key] = src['get_' + key]();
    };
};

var Fields = SimpleClass(fields);

Exercise: write test

Here’s an exercise. Write a JavaScript file that tests this solution.

Exercise: bound method class

Recall that this-based instance methods are somewhat fragile. In other words, code like this will fail (and make unwanted changes to the global object):

var instance = MyClass(arg1, arg2);

var fn = instance.method;
element.onclick(fn);

Let’s say that a class has bound methods if we can safely pass around instance methods. In other words, we want instance.method to have no dependence on the value of this.

We can achieve this by using a different class factory function.

var myclass = {}    // The prototype object.
// Add methods to myclass.
var MyClass = BoundMethodClass(myclass);

The exercise is to create (and test) a BoundMethodClass factory.