9

Перегрузка функций в JavaScript - лучшие практики [закрыто]

3

Проблема: Как имитировать перегрузку функций в JavaScript?

Я понимаю, что перегрузка функций в JavaScript, как в других языках, невозможна. Однако у меня есть функция, которая должна работать по двум сценариям: с одним параметром foo(x) и с тремя параметрами foo(x,y,z). Какой из приведенных ниже способов является наилучшим или предпочтительным для реализации этой функции?

  1. Использовать разные имена функций изначально.
  2. Использовать необязательные аргументы, например: y = y || 'default'.
  3. Основываться на количестве переданных аргументов.
  4. Проверять типы переданных аргументов.
  5. Или есть другие способы?

Заранее спасибо за советы!

5 ответ(ов)

7

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

Большинство разработчиков в таких случаях добавляют объект в качестве последнего аргумента своих методов. Этот объект может содержать любые данные.

Вот пример:

function foo(a, b, opts) {
  // ...
  if (opts['test']) { } // Если параметр test существует, делаем что-то...
}

foo(1, 2, {"method":"add"});
foo(3, 4, {"test":"equals", "bar":"tree"});

Таким образом, вы можете обрабатывать параметры любым удобным для вас способом в методе (с помощью switch, if-else и т.д.).

1

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

public string CatStrings(string p1)                  { return p1; }
public string CatStrings(string p1, int p2)          { return p1 + p2.ToString(); }
public string CatStrings(string p1, int p2, bool p3) { return p1 + p2.ToString() + p3.ToString(); }

CatStrings("one");        // результат = one
CatStrings("one", 2);     // результат = one2
CatStrings("one", 2, true); // результат = one2true

В JavaScript, как вы продемонстрировали, можно добиться аналогичного поведения с помощью проверки на undefined:

function CatStrings(p1, p2, p3) {
  var s = p1;
  if (typeof p2 !== "undefined") { s += p2; }
  if (typeof p3 !== "undefined") { s += p3; }
  return s;
};

CatStrings("one");        // результат = one
CatStrings("one", 2);     // результат = one2
CatStrings("one", 2, true); // результат = one2true

Вы правы, что использование undefined в JavaScript делает код более элегантным, так как проверка на undefined позволяет избежать дополнительных перегрузок. Однако следует помнить, что функция не передает явно информацию о том, что p2 и p3 являются необязательными параметрами. Для более сложных случаев, как вы отметили, jQuery использует объект в качестве параметра (например, jQuery.ajax(options)), что является более мощным и документируемым решением перегрузки методов. Тем не менее, если вам нужно всего лишь несколько необязательных параметров, то предложенный вами способ будет вполне достаточным.

1

Правильный ответ: В JAVASCRIPT НЕТ ПЕРЕГРУЗКИ ФУНКЦИЙ.

Проверка или переключение внутри функции — это не перегрузка.

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

Например, doTask() и doTask(object O) — это перегруженные методы. Чтобы вызвать второй метод, необходимо передать объект в качестве параметра, тогда как первый не требует параметра и вызывается с пустым полем параметров. Типичной ошибкой было бы присвоение значения по умолчанию объекту во втором методе, что приведет к ошибке неоднозначного вызова, поскольку компилятор не сможет определить, какой из двух методов использовать.

https://en.wikipedia.org/wiki/Function_overloading

Все предложенные реализации хороши, но, по правде говоря, в JavaScript нет нативной реализации перегрузки функций.

0

Существует два способа подойти к этой задаче более эффективно:

  1. Передайте словарь (ассоциативный массив), если вы хотите оставить больше гибкости.
  2. Используйте объект в качестве аргумента и примените наследование на основе прототипов для увеличения гибкости.
0

Вот подход, позволяющий реализовать реальную перегрузку методов с использованием типов параметров, как показано ниже:

Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);

Дополнение (2018): С момента написания этого ответа в 2011 году скорость прямых вызовов методов значительно возросла, тогда как скорость перегруженных методов осталась на прежнем уровне.

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


Вот бенчмарк различных подходов - https://jsperf.com/function-overloading. Он показывает, что перегрузка функций (с учетом типов) может быть примерно 13 раз медленнее в Google Chrome's V8 на версии 16.0(beta).

Помимо передачи объекта (т.е. {x: 0, y: 0}), также можно применить подход из C, назвав методы соответствующим образом. Например, Vector.AddVector(vector), Vector.AddIntegers(x, y, z, ...) и Vector.AddArray(integerArray). Вы можете вдохновиться названиями из библиотек на C, таких как OpenGL.

Дополнение: Я добавил бенчмарк для передачи объекта и проверки объекта с использованием как 'param' in arg, так и arg.hasOwnProperty('param'), и перегрузка функций оказывается намного быстрее, чем передача объекта и проверка свойств (по крайней мере, в этом бенчмарке).

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

Мой пример основан на реализации прямоугольника, поэтому упоминаются Dimension и Point. Возможно, Rectangle мог бы добавить метод GetRectangle() в прототипы Dimension и Point, после чего проблема с перегрузкой функций будет решена. А что насчет примитивов? У нас есть длина аргументов, что теперь является допустимой проверкой, поскольку у объектов есть метод GetRectangle().

function Dimension() {}
function Point() {}

var Util = {};

Util.Redirect = function (args, func) {
  'use strict';
  var REDIRECT_ARGUMENT_COUNT = 2;

  if(arguments.length - REDIRECT_ARGUMENT_COUNT !== args.length) {
    return null;
  }

  for(var i = REDIRECT_ARGUMENT_COUNT; i < arguments.length; ++i) {
    var argsIndex = i-REDIRECT_ARGUMENT_COUNT;
    var currentArgument = args[argsIndex];
    var currentType = arguments[i];
    if(typeof(currentType) === 'object') {
      currentType = currentType.constructor;
    }
    if(typeof(currentType) === 'number') {
      currentType = 'number';
    }
    if(typeof(currentType) === 'string' && currentType === '') {
      currentType = 'string';
    }
    if(typeof(currentType) === 'function') {
      if(!(currentArgument instanceof currentType)) {
        return null;
      }
    } else {
      if(typeof(currentArgument) !== currentType) {
        return null;
      }
    } 
  }
  return [func.apply(this, args)];
}

function FuncPoint(point) {}
function FuncDimension(dimension) {}
function FuncDimensionPoint(dimension, point) {}
function FuncXYWidthHeight(x, y, width, height) { }

function Func() {
  Util.Redirect(arguments, FuncPoint, Point);
  Util.Redirect(arguments, FuncDimension, Dimension);
  Util.Redirect(arguments, FuncDimensionPoint, Dimension, Point);
  Util.Redirect(arguments, FuncXYWidthHeight, 0, 0, 0, 0);
}

Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);

Надеюсь, это поможет в понимании темы и решении вопросов с перегрузкой методов!

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь