JS задачка

12
ArbNet
На сайте с 27.10.2019
Offline
145
869

Это для вас сложно, но всё же рискну 😀

У меня есть модуль для создания обработчиков событий Events. На странице есть элементы у которых есть атрибуты в которых прописаны функции как например onclick(поясню специально, это нестандартные события, например для наблюдения изменения дочерних элементов и др). Модуль событий находит такие элементы и создаёт функции обработчики и вешает на эти элементы. Всё замечательно.

Но когда используются глобальные переменные, например, создал переменную в глобальной области и хочешь её использовать, но она 'is not defined', т.к. при создании функции нужно передавать эти переменные, но мы заранее не знаем какие, поэтому нужен универсальный подход.

let aa='AAA',
    sf='console.log(ee+aa)';
function Nf(){
  return new Function('ee',sf)
}
let Fu=Nf();
Fu('BBB')

Для любителей спрашивать у ИИ скажу, что eval, with, замыкания тут не подходят, нужно именно через new Function, которая в своей области видимости будет видеть глобальные переменные.

ЗЫ. Вот почему я в таких случаях недолюбливаю разработчиков существующих инструментов и ЯП..

ZEEW
На сайте с 03.06.2018
Offline
78
#1

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

Проблема в том, что new Function() создает функцию в глобальной области видимости, но не имеет доступа к переменным из областей видимости, в которых она была создана (кроме глобальных).

Вот вариант решения:

// Создаем функцию, которая получит доступ к глобальному объекту
function createFunctionWithGlobals(code, paramNames = [], paramValues = []) {
  // Получаем глобальный объект (window в браузере или global в Node.js)
  const globalObj = typeof window !== 'undefined' ? window :
                   typeof global !== 'undefined' ? global : this;
  
  // Собираем имена всех глобальных переменных
  const globalVarNames = Object.keys(globalObj)
    .filter(key => !['undefined', 'NaN', 'Infinity'].includes(key) &&
            typeof key === 'string' && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key));
  
  // Создаем массив со значениями этих переменных
  const globalVarValues = globalVarNames.map(name => globalObj[name]);
  
  // Объединяем параметры функции с глобальными переменными
  const allParamNames = [...paramNames, ...globalVarNames];
  const allParamValues = [...paramValues, ...globalVarValues];
  
  // Создаем функцию с доступом к глобальным переменным
  try {
    return new Function(...allParamNames, code).bind(null, ...allParamValues);
  } catch (e) {
    console.error("Ошибка при создании функции:", e);
    return () => console.error("Функция не была создана из-за ошибки:", e);
  }
}

// Пример использования:
let aa = 'AAA';
let sf = 'console.log(ee + aa)';

// Создаем функцию с доступом к глобальным переменным
const Fu = createFunctionWithGlobals(sf, ['ee']);

// Вызываем функцию
Fu('BBB'); // Выведет "BBBAAA"

Что делает это решение:

  1. Определяет глобальный объект (window или global)
  2. Собирает имена и значения всех подходящих глобальных переменных
  3. Создает функцию, передавая ей как параметры и глобальные переменные, и пользовательские параметры

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

Станислав
На сайте с 27.12.2009
Offline
258
#2

Ничего не понятно на самом деле =) Но если че то не получается, значит не правильный подход и надо делать по другому, а не городить костыли =)

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

Мы там, где рады нас видеть.
ZEEW
На сайте с 03.06.2018
Offline
78
#3
Станислав #:

Ничего не понятно на самом деле =) Но если че то не получается, значит не правильный подход и надо делать по другому, а не городить костыли =)

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

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

По поводу глобальных переменных - они доступны в любой момент после их объявления. Проблема в том, что когда вы создаете функцию через new Function() , она имеет доступ только к глобальной области видимости, но не к локальной области, где была создана.

В примере:

let aa='AAA',
    sf='console.log(ee+aa)';
function Nf(){
  return new Function('ee',sf)
}
let Fu=Nf();
Fu('BBB')

Переменная aa объявлена в глобальной области, но внутри new Function() она не видна, потому что функция, созданная через new Function() , имеет доступ только к глобальному объекту (window в браузере).

Вот более простой подход без костылей:

// Глобальные переменные должны быть свойствами глобального объекта
window.aa = 'AAA'; // Для браузера, если это Node.js, то global.aa = 'AAA'

let sf = 'console.log(ee+aa)';

function Nf(){
  return new Function('ee', sf)
}

let Fu = Nf();
Fu('BBB'); // Теперь должно вывести "BBBAAA"

Главное различие: let aa = 'AAA' создает переменную в текущей области видимости, а window.aa = 'AAA' создает свойство глобального объекта, которое будет доступно внутри функции, созданной через new Function() .

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

ArbNet
На сайте с 27.10.2019
Offline
145
#4
ZEEW #:

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

Проблема в том, что new Function() создает функцию в глобальной области видимости, но не имеет доступа к переменным из областей видимости, в которых она была создана (кроме глобальных).

Я неправильно в первом сообщении написал задачу. Там я намного всё упростил, чтобы попонятнее, но вышло наоборот. Пример выше рабочий. У меня же немного иначе это происходит через промисы, примерно так:

new Promise(function(resolve, reject){
let aa='AAA';
  resolve('console.log(ee+aa)');
}).then((sf)=>{
  function Nf(){
    return new Function('ee',sf)
  }
  let Fu=Nf();
  Fu('BBB')
})

Опять же я тут всё сильно упростил. Смысл в том, что у меня в начале, в хедере страницы стоит загрузчик модулей, запускается скрипт и загружает указанные модули создав промис. В конце страницы пользовательские скрипты, которые запускаются в этом промисе после того как страница и все JS модули загружены.

Так вот нужен доступ к переменным промиса при new Function в модуле событий. Опять же мы не можем передавать переменные по цепочке промиса и в модули, т.к. заранее не знаем какие будут вообще переменные.

ZEEW
На сайте с 03.06.2018
Offline
78
#5
ArbNet #:

Я неправильно в первом сообщении написал задачу. Там я намного всё упростил, чтобы попонятнее, но вышло наоборот. Пример выше рабочий. У меня же немного иначе это происходит через промисы, примерно так:

Опять же я тут всё сильно упростил. Смысл в том, что у меня в начале, в хедере страницы стоит загрузчик модулей, запускается скрипт и загружает указанные модули создав промис. В конце страницы пользовательские скрипты, которые запускаются в этом промисе после того как страница и все JS модули загружены.

Так вот нужен доступ к переменным промиса при new Function в модуле событий. Опять же мы не можем передавать переменные по цепочке промиса и в модули, т.к. заранее не знаем какие будут вообще переменные.

Теперь я лучше понимаю вашу задачу. Проблема в том, что переменные, объявленные внутри Promise (как aa в вашем примере), находятся в замкнутой области видимости этого Promise и недоступны для функций, создаваемых через new Function() позже.

В JavaScript существует фундаментальное ограничение: функции, созданные через new Function() , имеют доступ только к глобальной области видимости (например, window в браузере), но не к локальным переменным или замыканиям.

Вот несколько возможных подходов к решению:

  1. Хранить все нужные переменные в глобальном объекте (не очень хороший подход с точки зрения архитектуры):
// Создаем глобальный объект для хранения данных
window.appContext = {};

new Promise(function(resolve, reject){
  // Сохраняем переменную в глобальный объект
  window.appContext.aa = 'AAA';
  resolve('console.log(ee+window.appContext.aa)');
}).then((sf)=>{
  function Nf(){
    return new Function('ee', sf)
  }
  let Fu = Nf();
  Fu('BBB')
})

2. Передавать контекст в виде объекта в обработчики:

new Promise(function(resolve, reject){
  let aa = 'AAA';
  // Передаем как код, так и данные для выполнения
  resolve({
    code: 'console.log(ee+context.aa)',
    context: { aa }
  });
}).then((data)=>{
  function Nf(){
    // Создаем функцию, которая примет контекст
    return new Function('ee', 'context', data.code)
  }
  let Fu = Nf();
  Fu('BBB', data.context)
})

3. Использовать паттерн "Внедрение зависимостей" для ваших модулей:

// Система модулей
const ModuleSystem = {
  modules: {},
  context: {},
  
  register(name, factory) {
    this.modules[name] = factory;
  },
  
  setContext(contextData) {
    Object.assign(this.context, contextData);
  },
  
  getModule(name) {
    if (!this.modules[name]) throw new Error(`Module ${name} not found`);
    return this.modules[name](this.context);
  }
};

// Регистрация модуля событий
ModuleSystem.register('events', (context) => {
  return {
    createHandler(code) {
      // Используем контекст при создании функции
      return new Function('event', 'context', code);
    },
    
    bindHandler(element, eventName, handler) {
      element.addEventListener(eventName, (e) => {
        // Передаем контекст в обработчик
        handler(e, context);
      });
    }
  };
});

// Использование
new Promise(function(resolve) {
  let aa = 'AAA';
  // Добавляем aa в контекст
  ModuleSystem.setContext({ aa });
  resolve();
}).then(() => {
  // Получаем модуль событий с контекстом
  const events = ModuleSystem.getModule('events');
  
  // Создаем обработчик
  const handler = events.createHandler('console.log(event + context.aa)');
  
  // Привязываем обработчик
  events.bindHandler(document.getElementById('myElement'), 'click', handler);
});

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

Это более чистый подход с архитектурной точки зрения, чем использование глобальных переменных.

ArbNet
На сайте с 27.10.2019
Offline
145
#6
Это всё не то.. по моему вы пользуетесь ИИ
Это очень примитивные подходы, не хочу так делать.
ZEEW
На сайте с 03.06.2018
Offline
78
#7
ArbNet #:
Это всё не то.. по моему вы пользуетесь ИИ
Это очень примитивные подходы, не хочу так делать.

Если вам нужно, чтобы динамически созданная функция через new Function() имела доступ к переменным из области видимости промиса, то это действительно фундаментальное ограничение JavaScript - функции, созданные через new Function() , видят только глобальную область видимости.

Возможно, стоит пересмотреть архитектуру вашего решения в целом. Может быть, более подходящим будет подход, где обработчики событий используют другие механизмы, например:

  1. Компилятор или транспилятор для преобразования кода атрибутов в работающий код
  2. Система на основе действий/команд, где в атрибутах указывается не код, а идентификаторы действий
  3. Механизм, основанный на шаблонах или DSL

Без более детального понимания вашей системы и требований трудно предложить оптимальное решение.

Search Google
На сайте с 14.01.2017
Offline
150
#8
ArbNet #:
Это всё не то.. по моему вы пользуетесь ИИ

Да ладноо..., как ты догадался?😂

Z0
На сайте с 03.09.2009
Offline
826
#9
Вы бы пример конкретный привели, из вашего ТЗ ваще ничего непонятно =)
S3
На сайте с 29.03.2012
Offline
366
#10
ziliboba0213 #:
Вы бы пример конкретный привели, из вашего ТЗ ваще ничего непонятно =)

Так ТС сам не понимает, что хочет, уже полгода. Его грязными тряпками выкинули с кибера, но общения хочется - сюда пришел.

У любой задачи есть бизнес-ценность. То есть - какой результат будет в результате решения задачи.
Любая задача должна быть понятно описана. Тогда ее можно решить. 

В данном случае нет ничего. Что улучшит решение этой проблемы по сравнению с существующими? Ничего
Описание понятное - отсутствует. Если возникает задача сложная для нее рисуют ся диаграммы, описывается существующая ситуация, описывается желаемый результат. 

То есть - страничка сайта, где есть проблема и что должно получиться после решения. 

А гадать, что там в голове у ТС - нет ни желания не времени. Тем более ему уже массу вариантов предлагали решения.

12

Авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий