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