JS| Инициализированный объект не определен после того, как функция, обращающаяся к нему, запускается более одного раза

Я пытаюсь уменьшить глобальные переменные в своем расширении Chrome, чтобы уменьшить "неплотность" или неоднозначность в моем JavaScript.

Я пытаюсь сделать это, имея функцию инициализации, которая объявляет эти глобальные переменные в противном случае в функции обратного вызова mousedown eventListener, которая, вероятно, будет запущена более одного раза.

Это сделано для того, чтобы я мог передавать эти переменные + события другим обратным вызовам eventListener (а именно обратному вызову mouseup) в качестве аргументов таких обратных вызовов.

Я разложил задачу в отдельный файл:

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();
}

Метод, который я использовал для управления выполнением initVariables() - глобальная логическая переменная, isVarsInitOnce который эффективен при инициализации переменных и настройке объекта для использования в executesMoreThanOnce() работать один раз.

К объектам можно получить доступ в первом случае, когда функция вызывается в for цикл, однако объекты undefined когда к ним пытаются получить доступ во вторичном экземпляре, который функция вызывается в for петля.

Это довольно четко обозначено в выводе консоли:

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

Я не понимаю, почему это происходит.

Может ли кто-нибудь помочь мне понять, почему это не работает должным образом?

Есть ли у кого-нибудь лучшее предложение по уменьшению глобальных переменных в отношении моего случая?

Большое спасибо.

2 ответа

Решение

Я ознакомился с некоторыми материалами, посвященными области действия и закрытию в JS, и думаю, что нашел решение разложенной проблемы в моем исходном вопросе.

Ответ, данный Абером Абу-Рахмой, не решает мою проблему, потому что переменные сбрасываются при каждом вызове функции executesMoreThanOnce().

Это не то, что я искал, потому что я хотел, чтобы переменные были изначально установлены только в первом экземпляре вызова функции (потому что в моем реальном проекте executesMoreThanOnce() по сути, представляет собой обратный вызов события щелчка, который требует, чтобы данные сохранялись при повторном запуске события).

В моем решении используется выражение немедленного вызова функции (IIFE). чтобы инициализировать переменные локально в рамках IIFE и выпустить переменные в глобальную область в возвращенном get метод:

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);
}

Теперь вы можете видеть в консоли, что TEST.executesMoreThanOnce() функция начинает использовать начальные переменные, определенные локально в пределах TEST Функция IIFE:

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}

Теперь мы также можем видеть, что если значение i соответствует определенным условиям в TEST.executesMoreThanOnce() функции, логические значения начинают переключаться, но, что более важно, эти изменения сохраняются между вызовами функций.

Я все еще не уверен, что полностью понимаю абстракцию, лежащую в основе IIFE. но я ясно вижу в коде, какие переменные относятся к области действия, в которых они используются (улучшая читаемость). Это была моя цель в первую очередь.

Если кто-то захочет поправить меня в чем-то, что я неправильно понял, добро пожаловать, прежде чем я начну пытаться реализовать это в своем проекте.

Во-первых, как booleanObject, так и eventObject должны быть объявлены вне оператора if, чтобы быть в области видимости. Во-вторых, если вам нужно запускать executesMoreThanOnce более одного раза, вам нужно установить isVarsInitOnce в false в цикле for

           let  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
        }
    };
      let booleanObject ; // declaring booleanObject outside the if statement
        let eventObject ; // declaring eventObject outside the if statement
    if (!isVarsInitOnce) {
        isVarsInitOnce = true;
        let vars = initVariables();

        booleanObject = vars.bools;
        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 5 times
for (let i=0; i<5; i++) {// Will execute 5 times
    executesMoreThanOnce();
    isVarsInitOnce = false;
}

редактировать

Извините, я не совсем понял ваши требования. Обратите внимание на следующее:

JavaScript

    // This will be the object that holds your variables
     let vars;
       
       function executesMoreThanOnce(obj) {
           const initVariables = function(obj) {
              
               console.log('runs once and obj = ' + obj);
               if (obj == undefined) {
                
                console.log('initialize once');
                    const bools = {
                        boolOne: false,
                        boolTwo: false,
                        boolThree: false
                    };
            
                    const events = {
                        clickEvent:
                            function(event) {
                                //do stuff
                            },
                        keyEvent:
                            function(event) {
                                //do other stuff
                            }
                    };
                   
                   return (function(){
                        return {bools: bools,
                                events: events};
                    })();
                }
                // If vars object, "which we passed as argument", is initialized once before just return it
                return vars;
               
           };

           vars = initVariables(vars);
               
           let    booleanObject = vars.bools;
           let    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.
           // Yes you can now access the variables
           booleanObject.boolOne = true;
       }
       
       //runs 5 times
       for (let i=0; i<5; i++) {// Will execute 5 times
            // Removed the bool flag and now passing the vars object as argument
           executesMoreThanOnce(vars);
          
           //executesMoreThanOnce();
           //isVarsInitOnce = false;
       }

Проверьте это на Fiddler

Другие вопросы по тегам