简体   繁体   中英

Javascript: Dynamically add functions to object

I have a GeneralWrapper object that calls the statically-defined functions in the Library1 and Library2 objects.

The aim is that by calling GeneralWrapper.someFunc() , this will also call Library1.someFunc() and Library2.someFunc() without me having to explicitly create a function in GeneralWrapper called someFunc .

I attempt to implement this in the __preamble method below:

var GeneralWrapper = {

    __modespopulated: false,

    __validmodes: {     // All 3 of these
        spoonFunc: 1,   //  functions exist
        knifeFunc: 1,   //  in Library1 and
         forkFunc: 1    //  Library2
    },

    __switchMode: function(funcname){

        if (funcname in GeneralWrapper.__validmodes){
            console.log("calling function", funcname)

            GeneralWrapper.__preamble()

            Library1[ funcname ](); // Call mode in Library1
            Library2[ funcname ](); // Call mode in Library2
        }
    },

    /* Attach valid modes to General Wrapper at runtime */
    __preamble: function(){
        if (!GeneralWrapper.__modespopulated)
        {
            for (var mode in GeneralWrapper.__validmodes)
            {
                GeneralWrapper[mode] = function(){
                    GeneralWrapper.__switchMode(mode)
                };
            }

            GeneralWrapper.__modespopulated = true
        }

        GeneralWrapper.__otherprestuff();
    },


    __otherprestuff: function(){
        // Stuff
    },

    funcThatAlwaysGetsCalled: function(){
        GeneralWrapper.__switchMode("forkFunc");
    }    
}

var Library1 = { 
     forkFunc(){console.log("Lib1","fork")},
    spoonFunc(){console.log("Lib1","spoon")},
    knifeFunc(){console.log("Lib1","knife")}
}

var Library2 = { 
     forkFunc(){console.log("Lib2","FORK")},
    spoonFunc(){console.log("Lib2","SPOON")},
    knifeFunc(){console.log("Lib2","KNIFE")}
}

// Okay, let's initialise the object
GeneralWrapper.funcThatAlwaysGetsCalled();

For some reason calls to GeneralWrapper.spoonFunc() and GeneralWrapper.knifeFunc() always defer to the Fork output.

I imagine the problem stems from the anonymous function assignment on the GeneralWrapper[mode] = blah line where JS treats it as the same function each time, but I don't know how to get around this.

Please advise.

Solution:

Change this line:

for (var mode in GeneralWrapper.__validmodes)

into this:

for (let mode in GeneralWrapper.__validmodes)

Explanation:

what happens in your code (when binding functions in __preamble 's loop) is that you create an anonymous function, which is totally fine. The problem is, your anon function has received the mode as a reference to local variable, so it's value is not automatically cloned but rather accessed at runtime. The main problem is that you've used var keyword, which means "hoisted variable" (it gets declared at the top of the function it was defined inside, even if it's somewhere in the middle of your function's code). In this scenario, you need a "block-scoped" variable, which will be bound to each loop iteration separately.

You can read more about variables hostings on MDN:
var at MDN
let at MDN

One thing you have to know - let was introduced in ES2015, so if you worry about backward compatibility with older browsers, you either have to use Function.prototype.bind or IIFE

One potential problem here is that you're creating functions inside a loop which can lead to some performance problems or unexpected behavior.

I'd replace:

for (var mode in GeneralWrapper.__validmodes)
{
    GeneralWrapper[mode] = function(){
        GeneralWrapper.__switchMode(mode)
    };
}

with:

for (var mode in GeneralWrapper.__validmodes)
{
    GeneralWrapper[mode] = GeneralWrapper.__switchMode.bind(this, mode);
}

Which should solve the problem at hand.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM