Лучшая практика для перебора массива функций с разными подписями в Javascript?

У меня есть массив функций javascript Functs = [f1, f2, f3...], и я хочу перебрать массив, последовательно выполняя каждую функцию.

Редактировать: в настоящее время у нас есть функции, вызываемые явно в цикле while(true){f1(a,b,c); f2(b); f3(c)...}. Однако мы хотели бы сделать этот конвейер расширяемым, чтобы пользователи могли вставлять пользовательские функции в конвейер на любом этапе, предоставляя общедоступный метод для вставки элементов в массив, в результате чего: while(true){f1(a,b,c); f2(b); fCustom(b,c); f3(c)...}. Итак, редактируемый извне пайплайн. Возможно, есть лучший способ сделать это, но редактируемый набор функций кажется самым простым способом.

Проблема в том, что каждая функция имеет свою сигнатуру и требует разных входных данных, например:

f1 = function(name, address, date) {};
f2 = function(name, age) {};
f3 = function(height);

И функции будут зацикливаться так:

mainFunction() {

  let name = something;
  let address = something;
  let date = something;
  ....
  let height = something;

  while(condition == true) {
    for(f in Functs) {
      f(?????); //Each f() has a different signature?
    } 
  }
}

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

Редактировать: учитывая список значений (имя, возраст, дату и т. д.), каждая функция обрабатывает этот блок данных в последовательном конвейере. Возможно, одна функция проверяет значение age и, если оно соответствует определенному условию, изменяет date. Следующая функция будет просматривать функции name и date и каким-то образом манипулировать ими, а затем перезаписывать name результатом. Пока один этап конвейера (возможно, после нескольких циклов) не обнаружит, что все полностью обработано. Таким образом, каждый этап должен иметь возможность изменять эти параметры, чтобы изменения были видны следующей функции в последовательности, то есть:f2 = function(height) {height += 2;}f3 = function(name, age, height){if (height > age) condition = false;}

mainFunction() {
  let params = {
    name : something,
    address : something,
    date : something,
    ...
    height : something
  };

  f1 = function(params) {
    f1Inner = function (params.name, params.address, params.date) {};
  }

  Functs = [f1, f2, f3...]

  while(condition == true) {
    for(f in Functs) {
      f(params);
    } 
  }
}

Где и как получить параметры для каждой функции?

VLAZ 11.12.2020 18:04

Знаете ли вы, что можете использовать специальную переменную arguments (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…‌​) в функции и даже не использовать подпись?

MetallimaX 11.12.2020 18:05

Вы можете использовать деструктурирование. f1 = function({name, address, date}) {} и так далее.

ptothep 11.12.2020 18:06

Нет f2 = (height) {height += 2;} не изменит ничего вне функции. Можете ли вы поделиться более подробной информацией о том, что вы на самом деле пытаетесь сделать? Это немного похоже на проблему XY.

VLAZ 11.12.2020 18:08

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

Bergi 11.12.2020 18:21

Можете ли вы добавить правильный пример того, что функции будут делать и возвращать? По крайней мере, для 2 функций. f2 = (height) {height += 2;} ничего не делает. Он просто увеличивает параметр

adiga 11.12.2020 18:22

@VLAZ Это для обработки большого блока данных в конвейере после последовательности шагов. Каждая функция обеспечивает определенный шаг конвейера, и в настоящее время у нас есть функции, вызываемые явно в цикле while(true){f1(a,b,c); f2(b); f3(c)...}. Однако мы хотели бы сделать этот конвейер расширяемым, чтобы пользователи могли вставлять пользовательские функции в конвейер на любом этапе, предоставляя общедоступный метод для вставки элементов в массив, в результате чего: while(true){f1(a,b,c); f2(b); fCustom(b,c); f3(c)...}. Я надеюсь, что это проясняет для вас.

Trevor Buckner 11.12.2020 19:12

@adiga Смотрите мою правку.

Trevor Buckner 11.12.2020 19:19
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
8
722
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

mainFunction() {
  let name = something;
  let address = something;
  let date = something;
  ....
  let height = something;
  const paramsObj = { name, address, date, height };
  for (const fn of Functs) {
    fn(paramsObj);
    // do whatever you need to do with the return value or loop logic
  }

и

f1 = function({ name, address, date }) {};
f2 = function({ name, age }) {};
f3 = function({ height });

Если вам нужно, чтобы эти параметры также были изменены при передаче, вы можете вместо этого деструктурировать в первой строке, что позволит вам изменить объект params, например:

f1 = function(params) {
  const { name, address, date } = params;
  if (someCondition) {
    params.height += 2;
  }
};

и тогда последующие вызовы увидят измененный параметр.

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

VLAZ 11.12.2020 18:06

Это по-прежнему действует, если OP использует lodash.cloneDeep (если он вложенный) или Object.assign (если мелкий) перед передачей объекта параметров в функцию. JSON.decode(JSON.stringify(options)) будет работать вместо lodash, если все параметры являются простыми объектами, массивами или примитивами.

Charlie Bamford 11.12.2020 18:21

@CharlesBamford Я думаю, что OP хочет сохранить предыдущие значения при выполнении следующей функции. Итак, требуется мутация объекта.

adiga 11.12.2020 18:34

const if_then = (if_fn, then_fn) => (params_object) =>
  if_fn(params_object) ? then_fn(params_object) : null;

const apply_params = (fn, arg_keys, write_to_key = false) => (params_object) => {
  const result = fn(...arg_keys.map((v) => params_object[v]));
  if (write_to_key)
    params_object[write_to_key] = result;
  return result;
};

const name_and_age = (name, age) => name + ' is ' + age + ' and a half years old';
const volume = (height, width, depth) => height * width * depth;
const double_height = (height) => height * 2;
const double_width = (width) => width * 2;

const count_loop = (params_object) => Object.assign(params_object, { loop_count: (params_object.loop_count ?? 0) + 1 })

let functs = [
  count_loop,
  apply_params(double_height, ['height'], 'height'),
  if_then(
    (p) => !p.name_and_age,
    apply_params(name_and_age, ['name', 'age'], 'name_and_age'),
  ),
];

const bob = true;
if (bob)
  functs.push(
    if_then(
      (p) => p.loop_count > 2,
      apply_params(double_width, ['width'], 'width'),
    )
  );

functs = functs.concat([
  apply_params(volume, ['height', 'width', 'depth'], 'volume'),
  console.info
]);

let params = {
  name : 'Orbold the Grim',
  age : 145,
  height: 2,
  width: 0.3,
  depth: 0.2,
};

const should_exit_loop = (params_object) =>
  (params_object.loop_count || 0) > 10 || params_object.height > 8;

while(!should_exit_loop(params)) {
  functs.forEach(
    (f) => f(params)
  );
};

Умно, но на данный момент, почему бы просто не вызывать функции по отдельности?

Charlie Bamford 11.12.2020 18:25

Вероятно, вы можете заменить сокращение на params_list.map(p => params_object[p])

adiga 11.12.2020 18:25

@adiga о да ... спасибо, я обновил его.

Ben Stephens 11.12.2020 18:26
Ответ принят как подходящий

После большого количества тестов и бенчмаркинга самый быстрый метод, который мы придумали, это (используя решение @certainPerformance в качестве отправной точки):

  1. Храните функции в массиве объектов. Это позволяет пользователю вставлять новые пользовательские функции в любом месте списка (вставлять перед «taskA» и т. д.) и намного быстрее, чем объект объектов или Карта.
  2. Назовите задачи, используя array.some(). Несколько быстрее, чем for of или for in, и даже немного быстрее, чем array.map.
  3. Используйте function.call() для вызова функций. Это позволяет использовать переменную this, например, если вам нужен доступ к другим функциям класса. Если вы сделаете это, вы также должны передать this в array.some(), что сохранит контекст this без необходимости bind(this), что казалось довольно медленным.
  4. Для параметров, используемых только несколькими функциями, используйте объект params, который дает общую сигнатуру функции, позволяя пользовательским функциям получать доступ к любым соответствующим переменным. Параметры, общие для всех функций, передаются напрямую (для скорости, особенно при изменении этого значения).
//=== List of Pipeline Steps ===//
this.pipelineTasks = [
      { name: 'taskA', func: this.taskA },
      { name: 'taskB', func: this.taskB },
      { name: 'taskC', func: this.taskC }
    ];

// TaskA
TaskA(src, params) {
  return src + params.title;
}

// TaskB
TaskB(src, params) {
  return params.name ? params.name : params.name + "chicken";
}

// TaskC
TaskC(src, params) {
  params.age = params.age + params.shoeSize;
  return params.age > params.shoeSize ? params.age : params.shoeSize;
}

//=== Execute the processing pipeline ===//
processPipeline(src, name, age, shoeSize, title) {
  const myParams = {
    name     : name,
    age      : 10,
    shoeSize : 20,
    title    : title
  };

  while (myParams.age < 100) {
    this.pipelineTasks.some(function(fn) {
      return result = fn.func.call(this, src, myParams);
    }, this));
    
    //Alternatively, with arrow functions
    //  (slightly slower in Chrome, significantly slower in Node with hundreds
    //  of function calls, but doesn't require 'this' in array.some )
    // this.pipelineTasks.some((fn) => result = fn.func.call(this, src, myParams));
  }
}

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