Refer to the main JavaScript object from functions

2019-08-18 23:24发布

问题:

While working with javascript object I came to this code :

var mainModule = {
    opt : {
        opt1: 'my option1',
        opt2: 'my option2',
        opt3: 'my option3',
    },
    init: function(options){
        jQuery.extend(this.opt, options);
        this.mainMethod();
    },
    mainMethod: function() {
        //do Stuff
        var color = this.opt.opt1;
    },
    secondaryMethod1: function(){/*stuff*/},
    secondaryMethod2: function(){/*stuff*/},
    secondaryMethod3: function(){/*stuff*/},
    secondaryMethod4: function(){/*stuff*/},

    thirdlyMethod1: function(){/*stuff*/},
    thirdlyMethod2: function(){/*stuff*/},
    thirdlyMethod3: function(){/*stuff*/},
    thirdlyMethod4: function(){/*stuff*/},
};

With this code I often check the opt object with this.opt as this is mainModule. But all the code begin to be a littre messy with all the different method so I ended with this new code whith a new level of depth in the main object.

var mainModule = {
    opt : {
        opt1: 'my option1',
        opt2: 'my option2',
        opt3: 'my option3',
    },
    init: function(options){
        jQuery.extend(this.opt, options);
        this.mainMethod.init();
    },
    mainMethod: {
        init: function() {
        //do Stuff
        var color = mainModule.opt.opt1;
        },
        other: function(){},
        functions: function(){},
        here: function() {}
    },
    secondary: {
        method1: function(){/*stuff*/},
        method2: function(){/*stuff*/},
        method3: function(){/*stuff*/},
        method4: function(){/*stuff*/},
    }
    thirdly: {
        Method1: function(){/*stuff*/},
        Method2: function(){/*stuff*/},
        Method3: function(){/*stuff*/},
        Method4: function(){/*stuff*/},
    }   
};

But with this new one I can't use this.opt because this isn't the mainModule anymore.

With this kind of object, is there a better way to retrieve the opt object ? Does this new level of depth is necessary or should I use maybe a pseudo namespace ?

回答1:

You could always set up the main module in an IIFE and store the options as a local variable in that function. Which looks like this:

var mainModule = (function() {
    // Keep the options out of the object that's returned.
    // Hides the options to stop things like mainModule.options.opt1 = 'EVIL';
    var opt = {};

    return { 
        init: function(options){
            jQuery.extend(opt, options);
            this.mainMethod.init();
        },
        mainMethod: {
            init: function() {
                console.log(opt);
            },
            other: function(){},
            functions: function(){},
            here: function() {}
        },
        secondary: {
            method1: function(){},
            method2: function(){},
            method3: function(){},
            method4: function(){},
        },
        thirdly: {
            Method1: function(){},
            Method2: function(){},
            Method3: function(){},
            Method4: function(){},
        }
    };
}());

Now all your functions can just reference opt - you don't even need this anymore.



回答2:

If you merely want to access the options, then storing them in a closure at the top level as @RobH suggests will work fine. However, if you want this to function "correctly" in general in your subobjects, essentially you need to bind the functions in them to the top-level this. The brute force way to do that is in your top-level init function:

for (fn in this.secondary) {
    this.secondary[fn] = this.secondary[fn].bind(this);
}

Or equivalent. You could write a bindAll method:

bindAll: function(section) {
    for (fn in section) {
        section[fn] = section[fn].bind(this);
    }
    return section;
}

Then do

this.bindAll(this.secondary);
this.bindAll(this.thirdly);

in your init routine.

None of this is ideal. Basically, the JS this mechanism is not friendly to the sort of name-spacing which you are trying to do, much as I sympathize. In JS, this exists in precisely one place and one place only which is inside a function that is directly on an object, and is not available "in the wild" while you are defining the object as a literal.

The exception is in a constructor. You could take advantage of this fact, and do

function MainModule() {
    this.secondary = {
        method1: function(){}.bind(this),
        ...
    };
}
var mainModule = new MainModule();

However, with this approach here's no way to put secondary in a prototype as you might prefer and it will live on each instance.

If it was me, I might just throw in the towel and go back to our original structure/naming.