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

#javascript #javascript-objects #javascript-scope

#javascript #javascript-объекты #javascript-область

Вопрос:

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

Я пытаюсь сделать это, используя функцию init, которая объявляет эти глобальные переменные в противном случае в функции обратного вызова 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
 

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

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

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

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

Ответ №1:

Сначала оба 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

Комментарии:

1. Затем переменные сбрасываются при каждом вызове функции executesMoreThanOnce . Я хочу инициализировать эти переменные только один раз при первом вызове функции

Ответ №2:

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

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

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

В моем решении используется выражение функции, вызываемое немедленно (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. но я могу четко видеть в коде, какие переменные относятся к области, в которой они используются (улучшая читаемость). Это было моей целью в первую очередь.

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