javascriptjavascript-objectsjavascript-scope

JS| Initialised object is undefined after function accessing it is run more than once


I am trying to reduce the global variables in my Chrome extension to reduce the 'spaghettiness' or ambiguity in my JavaScript.

I am trying to attempt this by having an init function that declares these otherwise global variables within a mousedown eventListener callback function that is likely to be fired more than once.

This is so that I can pass these variables + events through to other eventListener callbacks (namely a mouseup callback) as arguments of such callbacks.

I have decomposed the issue into a separate file:

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
</head>
<body>
  <script src="test.js"></script>
</body>
</html>

test.js

isVarsInitOnce = false;

function executesMoreThanOnce() {
    const initVariables = function() {
        console.log('runs once');

        const bools = {
            boolOne: false,
            boolTwo: false,
            boolThree: false
        };

        const events = {
            clickEvent:
                function(event) {
                    //do stuff
                },
            keyEvent:
                function(event) {
                    //do other stuff
                }
        };

        return {
            bools: bools,
            events: events
        }
    };

    if (!isVarsInitOnce) {
        isVarsInitOnce = true;
        let vars = initVariables();

        var booleanObject = vars.bools;
        var eventObject = vars.events;
    }

    console.log('objects: ', booleanObject, eventObject);

    //attempting to access initialised variable after function is executed more than once will cause an error.
    //this is because the booleanObject is now undefined.
    booleanObject.boolOne = true;
}

//runs twice
for (let i=0; i<2; i++) {
    executesMoreThanOnce();
}

The method I have used to control the execution of the initVariables() is a global boolean variable, isVarsInitOnce which is effective at intialising the variables and setting the object for use in the executesMoreThanOnce() function once.

The objects are able to be accessed in the first instance that the function is called in the for loop, however the objects are undefined when they are attempted to be accessed in the secondary instance that the function is called in the for loop.

This is signified in rather clearly in the console output:

runs once
test.js:38 objects:  {boolOne: false, boolTwo: false, boolThree: false} {clickEvent: ƒ, keyEvent: ƒ}
test.js:38 objects:  undefined undefined //<--- (function called 2nd time)
test.js:42 Uncaught TypeError: Cannot set property 'boolOne' of undefined
    at executesMoreThanOnce (test.js:42)
    at test.js:47

I am unsure why this is occurring.

Can anyone help me to understand why this does not work properly?

Does anyone have a better suggestion for reducing global variables with regards to my case?

Many thanks.


Solution

  • I have had a look at some reading on scope and closure in JS and I think I have found a solution to the decomposed problem in my original question.

    The answer given by Aber Abou-Rahma does not solve my issue because the variables are reset per function call of executesMoreThanOnce().

    This is not what I was looking for because I only wanted the variables to be initially set in the first instance that the function is called (because in my actual project the executesMoreThanOnce() function essentially represents a click event callback that requires the data to persist when the event is re-triggered).

    My solution uses an Immediately-invoked Function Expression (IIFE). to initialise the variables locally within the scope of the IIFE, and release the variables into the global scope in its returned get method:

    test.js

    const TEST = (function() {
        let booleans = {
            boolOne: false,
            boolTwo: false,
            boolThree: false
        };
    
        let events = {
            clickEvent:
                function(event) {
                    //do stuff
                },
            keyEvent:
                function(event) {
                    //do other stuff
                }
        };
    
        return {
            executesMoreThanOnce: function(booleans, events, index) {
                booleanObject = booleans;
                eventsObject = events;
    
                if (i == 2) {
                    booleanObject.boolTwo = true;
                }
                else if (i == 4) {
                    booleanObject.boolOne = true;
                    booleanObject.boolTwo = false;
                }
    
                console.log('booleanObject: ', booleanObject);
            },
            get variables() {
                return {
                    booleans,
                    events
                }
            }
        };
    }());
    
    for (var i=0; i<5; i++) {
        TEST.executesMoreThanOnce(TEST.variables.booleans, TEST.variables.events, i);
    }
    

    You can now see in the console that the TEST.executesMoreThanOnce() function starts using the initial variables defined locally within the TEST IIFE function:

    i = 0 | booleanObject:  {boolOne: false, boolTwo: false, boolThree: false}
    i = 1 | booleanObject:  {boolOne: false, boolTwo: false, boolThree: false}
    i = 2 | booleanObject:  {boolOne: false, boolTwo: true, boolThree: false}
    i = 3 | booleanObject:  {boolOne: false, boolTwo: true, boolThree: false}
    i = 4 | booleanObject:  {boolOne: true, boolTwo: false, boolThree: false}
    

    We can also now see that once the i value meets certain conditions in the TEST.executesMoreThanOnce() function, the booleans begin to switch, but more importantly, these changes persist between function calls.

    I'm still not sure I understand the abstraction beneath the IIFE fully. but I can clearly see in the code which variables belong to the scope of which functions they are used in (improving readability). This was my goal in the first place.

    If anybody would like to correct me on something I have misunderstood you are more than welcome to before I begin trying to implement this into my project.