Перегрузка функций в Javascript - лучшие практики


784

Каков наилучший способ (ов) поддельной перегрузки функций в Javascript?

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

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

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

1
Это закрыто, но я делаю следующее: this.selectBy = {instance: selectByInstance, // Текст функции: selectByText, // Значение функции: selectByValue // Function};
Заключенный НОЛЬ

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

2
На всякий случай, если это полезно, я создал облегченный js-фреймворк, который допускает перегрузку методов на основе типов. Очевидно, что те же предостережения применяются в отношении производительности, но до сих пор она хорошо работала для моих нужд и все еще имеет много возможностей для улучшения: blog.pebbl.co.uk/2013/01/describejs.html#methodoverloading
Pebbl

Ответы:


603

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

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

function foo(a, b, opts) {
  // ...
  if (opts['test']) { } //if test param exists, do something.. 
}


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

Тогда вы можете справиться с этим так, как хотите в своем методе. [Переключение, если-еще, и т. Д.]


43
Не могли бы вы предоставить пример реализации функции foo (), которая иллюстрирует, как используются эти параметры "opts"?
Мо Говард

24
Мо // Это может быть так; if(opts['test']) //if test param exists, do something.. if(opts['bar']) //if bar param exists, do something
Deckard

80
Это не перегрузка функции. Перегрузка функций имеет две отдельные функции с одинаковым именем, но разными параметрами. То, что вы описываете - это всего лишь одна функция с аргументом объекта в конце.
d512

66
@ user1334007 невозможно перегрузить функцию, как если бы вы делали это в Java / .NET. Да, это не «точно» перегрузка, но это делает работу.
epascarello

18
Я удивлен, что никто уже не спрашивал об этом: почему проверка arguments.lengthне рекомендуется? Кроме того, я был здесь раньше и читал, что делают большинство разработчиков ... , но я уверен, что это единственное место, где я видел, что сделал. Этот метод также портит синтаксическую сладость наличия «перегрузок»!
c24w

169

Я часто делаю это:

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");        // result = one
CatStrings("one",2);      // result = one2
CatStrings("one",2,true); // result = one2true

Эквивалент JavaScript:

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

CatStrings("one");        // result = one
CatStrings("one",2);      // result = one2
CatStrings("one",2,true); // result = one2true

Этот конкретный пример на самом деле более элегантен в JavaScript, чем в C #. Параметры, которые не указаны, являются «неопределенными» в javascript, что приводит к значению false в операторе if. Однако определение функции не передает информацию о том, что p2 и p3 являются необязательными. Если вам нужно много перегрузок, jQuery решил использовать объект в качестве параметра, например, jQuery.ajax (options). Я согласен с ними, что это наиболее мощный и четко документируемый подход к перегрузке, но мне редко требуется более одного или двух быстрых необязательных параметров.

РЕДАКТИРОВАТЬ: изменил тест IF в соответствии с предложением Яна


16
Параметры, которые не указаны undefinedв JS, нет null. Лучше всего никогда не устанавливать ничего undefined, поэтому это не должно быть проблемой, пока вы меняете тест p2 === undefined.
Тамзин Блейк

3
Если вы передадите falseпоследний аргумент, он не будет объединен "false"до конца, потому что if(p3)не будет переходить .
Dreamlax

5
Просто короткое примечание, вы typeof p2 === "undefined", вероятно, противоположны тому, что вы ожидаете в случае с вашим примером, я думаю, typeof p2 !== "undefined"это то, что вы хотели. Кроме того, могу ли я предположить, что он должен объединять строку, число и логическое значение, которое вы на самом деле делаетеp2 === "number"; p3 === "boolean"
WillFM

8
Мне нравится это делать: p3 = p3 || 'значение по умолчанию';
Дориан

3
В чем смысл ===и !==? Почему бы просто не использовать ==и !=?
Рикардо Круз,

76

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


1
Джон Резиг (из jQuery) однажды попробовал это, но попытка была чисто академической и не принесла никакой реальной выгоды.
Scunliffe

14
Перегрузка функций Джона Резига
Терренс

@Terrance: мне тоже нравится метод Ресига. Работает как часы. Мне просто нужно найти способ создать для него тест для проверки вариантов использования.
chrisvillanueva

«Эта функция не изменит мир, но она коротка, лаконична и использует непонятную функцию JavaScript - поэтому она выигрывает в моей книге». :-)
Фрерих Раабе

68

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

Проверка / переключение внутри функции не является ПЕРЕГРУЗКОЙ.

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

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

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

Все предлагаемые реализации великолепны, но, по правде говоря, для JavaScript нет нативной реализации.


4
наконец то нормальный ответ! В JAVASCRIPT НЕТ ПЕРЕГРУЗКИ.
Разван Тудорица

4
ОП попросил способ подделать перегрузку.
Ateur Games

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

27

Есть два способа лучше подойти к этому:

  1. Передайте словарь (ассоциативный массив), если вы хотите оставить большую гибкость

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


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

19

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

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 V8 с 16.0 (бета) .

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

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

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

Мой пример взят из реализации Rectangle - отсюда и упоминание Dimension and 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);

16

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

  1. Использование необязательных аргументов, таких как y = y || 'дефолт'. Это удобно, если вы можете сделать это, но это не всегда может работать практически, например, когда 0 / null / undefined будет допустимым аргументом.

  2. Используя количество аргументов. Похож на последний вариант, но может работать, когда # 1 не работает.

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

  4. Использование разных имен в первую очередь. Вам может потребоваться сделать это, если другие опции не будут работать, не будут практичными или для соответствия другим связанным функциям.


14

Если мне нужна функция с двумя использует foo (x) и foo (x, y, z), что является лучшим / предпочтительным способом?

Проблема в том, что JavaScript НЕ поддерживает перегрузку методов. Таким образом, если он видит / анализирует две или более функций с одинаковыми именами, он просто учитывает последнюю определенную функцию и перезаписывает предыдущие.

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

Допустим, у вас есть метод

function foo(x)
{
} 

Вместо перегрузки метода, который невозможен в javascript, вы можете определить новый метод

fooNew(x,y,z)
{
}

а затем измените первую функцию следующим образом -

function foo(arguments)
{
  if(arguments.length==2)
  {
     return fooNew(arguments[0],  arguments[1]);
  }
} 

Если у вас много таких перегруженных методов, рассмотрите возможность использования switchне только if-elseоператоров.

( подробнее )

PS: выше ссылка идет на мой личный блог, который имеет дополнительные подробности.


9

Я не уверен в лучшей практике, но вот как я это делаю:

/*
 * Object Constructor
 */
var foo = function(x) {
    this.x = x;
};

/*
 * Object Protoype
 */
foo.prototype = {
    /*
     * f is the name that is going to be used to call the various overloaded versions
     */
    f: function() {

        /*
         * Save 'this' in order to use it inside the overloaded functions
         * because there 'this' has a different meaning.
         */   
        var that = this;  

        /* 
         * Define three overloaded functions
         */
        var f1 = function(arg1) {
            console.log("f1 called with " + arg1);
            return arg1 + that.x;
        }

        var f2 = function(arg1, arg2) {
             console.log("f2 called with " + arg1 + " and " + arg2);
             return arg1 + arg2 + that.x;
        }

        var f3 = function(arg1) {
             console.log("f3 called with [" + arg1[0] + ", " + arg1[1] + "]");
             return arg1[0] + arg1[1];
        }

        /*
         * Use the arguments array-like object to decide which function to execute when calling f(...)
         */
        if (arguments.length === 1 && !Array.isArray(arguments[0])) {
            return f1(arguments[0]);
        } else if (arguments.length === 2) {
            return f2(arguments[0], arguments[1]);
        } else if (arguments.length === 1 && Array.isArray(arguments[0])) {
            return f3(arguments[0]);
        }
    } 
}

/* 
 * Instantiate an object
 */
var obj = new foo("z");

/*
 * Call the overloaded functions using f(...)
 */
console.log(obj.f("x"));         // executes f1, returns "xz"
console.log(obj.f("x", "y"));    // executes f2, returns "xyz"
console.log(obj.f(["x", "y"]));  // executes f3, returns "xy"

2
@Luis: я добавил несколько, надеюсь, полезных комментариев.
шахматная паутина

6

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

TEST = {};

TEST.multiFn = function(){
    // function map for our overloads
    var fnMap = {};

    fnMap[0] = function(){
        console.log("nothing here");
        return this;    //    support chaining
    }

    fnMap[1] = function(arg1){
        //    CODE here...
        console.log("1 arg: "+arg1);
        return this;
    };

    fnMap[2] = function(arg1, arg2){
        //    CODE here...
        console.log("2 args: "+arg1+", "+arg2);
        return this;
    };

    fnMap[3] = function(arg1,arg2,arg3){
        //    CODE here...
        console.log("3 args: "+arg1+", "+arg2+", "+arg3);
        return this;
    };

    console.log("multiFn is now initialized");

    //    redefine the function using the fnMap in the closure
    this.multiFn = function(){
        fnMap[arguments.length].apply(this, arguments);
        return this;
    };

    //    call the function since this code will only run once
    this.multiFn.apply(this, arguments);

    return this;    
};

Попробуй это.

TEST.multiFn("0")
    .multiFn()
    .multiFn("0","1","2");

5

Поскольку в JavaScript нет функции перегрузки функции, можно использовать объект. Если есть один или два обязательных аргумента, лучше держать их отдельно от объекта параметров. Вот пример того, как использовать объект параметров и заполненные значения до значения по умолчанию в случае, если значение не было передано в объекте параметров.

    function optionsObjectTest(x, y, opts) {
        opts = opts || {}; // default to an empty options object

        var stringValue = opts.stringValue || "string default value";
        var boolValue = !!opts.boolValue; // coerces value to boolean with a double negation pattern
        var numericValue = opts.numericValue === undefined ? 123 : opts.numericValue;

        return "{x:" + x + ", y:" + y + ", stringValue:'" + stringValue + "', boolValue:" + boolValue + ", numericValue:" + numericValue + "}";

}

вот пример того, как использовать параметры объекта


4

Нет способа перегрузить функцию в javascript. Итак, я рекомендую использовать typeof()метод, а не несколько функций, чтобы имитировать перегрузку.

function multiTypeFunc(param)
{
    if(typeof param == 'string') {
        alert("I got a string type parameter!!");
     }else if(typeof param == 'number') {
        alert("I got a number type parameter!!");
     }else if(typeof param == 'boolean') {
        alert("I got a boolean type parameter!!");
     }else if(typeof param == 'object') {
        alert("I got a object type parameter!!");
     }else{
        alert("error : the parameter is undefined or null!!");
     }
}

Удачи!


24
Ради бога! Используйте оператор switch!
Джедмао

8
Кроме того, если вы настаиваете на том, чтобы не использовать переключатель, вы должны вызывать typeof только один раз. var type = typeof param; if (type === 'string') ...
Вальтер Стабош

+1 к комментарию за "===". Другое преимущество оператора switch перед if (... == ...) - это безопасность типов.
Натан Купер

4

ВВЕДЕНИЕ

Пока чтение стольких ответов вызовет головную боль. Любой, кто пытается узнать эту концепцию, должен знать следующие предварительные условия .

Function overloading Definition, Function Length property,Function argument property

Function overloadingв простейшем виде означает, что функция выполняет различные задачи на основе количества аргументов, которые ей передаются. В частности, TASK1, TASK2 и TASK3 выделены ниже и выполняются на основе количества argumentsпередаваемых одной и той же функции fooYo.

// if we have a function defined below
function fooYo(){
     // do something here
}
// on invoking fooYo with different number of arguments it should be capable to do different things

fooYo();  // does TASK1
fooYo('sagar'); // does TASK2
fooYo('sagar','munjal'); // does TAKS3

ПРИМЕЧАНИЕ. - JS не обеспечивает встроенную возможность перегрузки функций.

альтернатива

Джон Э Ресиг (создатель JS) указал на альтернативу, которая использует вышеуказанные предпосылки для достижения возможности перегрузки функций.

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

  • оценивает argument-lengthсвойство.
  • разные значения приводят к вызову разных функций.

var ninja = {
  whatever: function() {
       switch (arguments.length) {
         case 0:
           /* do something */
           break;
         case 1:
           /* do something else */
           break;
         case 2:
           /* do yet something else */
           break;
       //and so on ...
    } 
  }
}

Другая техника гораздо более чистая и динамичная. Изюминкой этой техники является addMethodобщая функция.

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

  • ниже addMethodфункция принимает три параметра имени объекта, имя objectфункции nameи функцию, которую мы хотим вызватьfn .

  • Внутри addMethodопределения var oldхранится ссылка на предыдущее function, хранящееся с помощью замыкания - защитного пузыря.

function addMethod(object, name, fn) {
  var old = object[name];
  object[name] = function(){
    if (fn.length == arguments.length)
      return fn.apply(this, arguments)
    else if (typeof old == 'function')
      return old.apply(this, arguments);
  };
};

  • используйте отладчик, чтобы понять поток кода.
  • ниже addMethodдобавляются три функции, которые при вызове используют ninja.whatever(x)с числом аргументов, xкоторое может быть любым, то есть либо пустым, либо одним или несколькими, вызывает различные функции, как определено при использовании addMethodфункции.

var ninja = {};
debugger;


addMethod(ninja,'whatever',function(){ console.log("I am the one with ZERO arguments supplied") });
addMethod(ninja,'whatever',function(a){ console.log("I am the one with ONE arguments supplied") });
addMethod(ninja,'whatever',function(a,b){ console.log("I am the one with TWO arguments supplied") });


ninja.whatever();
ninja.whatever(1,2);
ninja.whatever(3);


4

Другой способ сделать это - использовать специальную переменную: arguments , это реализация:

function sum() {
    var x = 0;
    for (var i = 0; i < arguments.length; ++i) {
        x += arguments[i];
    }
    return x;
}

так что вы можете изменить этот код:

function sum(){
    var s = 0;
    if (typeof arguments[0] !== "undefined") s += arguments[0];
.
.
.
    return s;
}

3

Проверь это. Это очень круто. http://ejohn.org/blog/javascript-method-overloading/ Уловка Javascript, позволяющая вам выполнять такие звонки:

var users = new Users();
users.find(); // Finds all
users.find("John"); // Finds users by name
users.find("John", "Resig"); // Finds users by first and last name

Привет, Jaider, проверь мой ответ, он содержит код для фактической перегрузки метода javascript. Я говорю Func(new Point())и Func(new Rectangle())буду выполнять разные функции. Но я должен отметить, что это грязный хак, поскольку перегрузка метода действительно является задачей времени компиляции, а не времени выполнения.
Келдон Аллейн

3

Перегрузка функций в Javascript:

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

Javascript не имеет встроенной функции перегрузки. Однако такое поведение можно эмулировать разными способами. Вот удобный простой:

function sayHi(a, b) {
  console.log('hi there ' + a);
  if (b) { console.log('and ' + b) } // if the parameter is present, execute the block
}

sayHi('Frank', 'Willem');

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

function foo (a, ...b) {
  console.log(b);
}

foo(1,2,3,4);
foo(1,2);


2

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

function onlyUnique(value, index, self) {
    return self.indexOf(value) === index;
}

function overload() {
   var functions = arguments;
   var nroffunctionsarguments = [arguments.length];
    for (var i = 0; i < arguments.length; i++) {
        nroffunctionsarguments[i] = arguments[i].length;
    }
    var unique = nroffunctionsarguments.filter(onlyUnique);
    if (unique.length === arguments.length) {
        return function () {
            var indexoffunction = nroffunctionsarguments.indexOf(arguments.length);
            return functions[indexoffunction].apply(this, arguments);
        }
    }
    else throw new TypeError("There are multiple functions with the same number of parameters");

}

это можно использовать как показано ниже:

var createVector = overload(
        function (length) {
            return { x: length / 1.414, y: length / 1.414 };
        },
        function (a, b) {
            return { x: a, y: b };
        },
        function (a, b,c) {
            return { x: a, y: b, z:c};
        }
    );
console.log(createVector(3, 4));
console.log(createVector(3, 4,5));
console.log(createVector(7.07));

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


2

Вы можете использовать «addMethod» от Джона Резига. С помощью этого метода вы можете «перегружать» методы на основе количества аргументов.

// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
    var old = object[ name ];
    object[ name ] = function(){
        if ( fn.length == arguments.length )
            return fn.apply( this, arguments );
        else if ( typeof old == 'function' )
            return old.apply( this, arguments );
    };
}

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

// addMethod - By Stavros Ioannidis
function addMethod(obj, name, fn) {
  obj[name] = obj[name] || function() {
    // get the cached method with arguments.length arguments
    var method = obj[name].cache[arguments.length];

    // if method exists call it 
    if ( !! method)
      return method.apply(this, arguments);
    else throw new Error("Wrong number of arguments");
  };

  // initialize obj[name].cache
  obj[name].cache = obj[name].cache || {};

  // Check if a method with the same number of arguments exists  
  if ( !! obj[name].cache[fn.length])
    throw new Error("Cannot define multiple '" + name +
      "' methods with the same number of arguments!");

  // cache the method with fn.length arguments
  obj[name].cache[fn.length] = function() {
    return fn.apply(this, arguments);
  };
}

2

Шаблон пересылки => наилучшая практика по перегрузке JS

Перейдем к другой функции, имя которой построено из 3-го и 4-го пунктов:

  1. Используя количество аргументов
  2. Проверка типов аргументов
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)

Заявка на ваше дело:

 function foo(){
          return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);

  }
   //------Assuming that `x` , `y` and `z` are String when calling `foo` . 

  /**-- for :  foo(x)*/
  function foo_1_string(){
  }
  /**-- for : foo(x,y,z) ---*/
  function foo_3_string_string_string(){

  }

Другой сложный образец:

      function foo(){
          return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);
       }

        /** one argument & this argument is string */
      function foo_1_string(){

      }
       //------------
       /** one argument & this argument is object */
      function foo_1_object(){

      }
      //----------
      /** two arguments & those arguments are both string */
      function foo_2_string_string(){

      }
       //--------
      /** Three arguments & those arguments are : id(number),name(string), callback(function) */
      function foo_3_number_string_function(){
                let args=arguments;
                  new Person(args[0],args[1]).onReady(args[3]);
      }

       //--- And so on ....   

2

Перегрузка функций через динамический полиморфизм в 100 строках JS

Это от большей части кода , который включает в себя isFn, isArrи т.д. функции проверки типов. Версия VanillaJS, приведенная ниже, была переработана для удаления всех внешних зависимостей, однако вам придется определить собственные функции проверки типов для использования в .add()вызовах.

Примечание. Это самореализуемая функция (поэтому мы можем иметь закрытое / закрытое пространство видимости), следовательно, присваивание window.overloadвместо function overload() {...}.

window.overload = function () {
    "use strict"

    var a_fnOverloads = [],
        _Object_prototype_toString = Object.prototype.toString
    ;

    function isFn(f) {
        return (_Object_prototype_toString.call(f) === '[object Function]');
    } //# isFn

    function isObj(o) {
        return !!(o && o === Object(o));
    } //# isObj

    function isArr(a) {
        return (_Object_prototype_toString.call(a) === '[object Array]');
    } //# isArr

    function mkArr(a) {
        return Array.prototype.slice.call(a);
    } //# mkArr

    function fnCall(fn, vContext, vArguments) {
        //# <ES5 Support for array-like objects
        //#     See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Browser_compatibility
        vArguments = (isArr(vArguments) ? vArguments : mkArr(vArguments));

        if (isFn(fn)) {
            return fn.apply(vContext || this, vArguments);
        }
    } //# fnCall

    //# 
    function registerAlias(fnOverload, fn, sAlias) {
        //# 
        if (sAlias && !fnOverload[sAlias]) {
            fnOverload[sAlias] = fn;
        }
    } //# registerAlias

    //# 
    function overload(vOptions) {
        var oData = (isFn(vOptions) ?
                { default: vOptions } :
                (isObj(vOptions) ?
                    vOptions :
                    {
                        default: function (/*arguments*/) {
                            throw "Overload not found for arguments: [" + mkArr(arguments) + "]";
                        }
                    }
                )
            ),
            fnOverload = function (/*arguments*/) {
                var oEntry, i, j,
                    a = arguments,
                    oArgumentTests = oData[a.length] || []
                ;

                //# Traverse the oArgumentTests for the number of passed a(rguments), defaulting the oEntry at the beginning of each loop
                for (i = 0; i < oArgumentTests.length; i++) {
                    oEntry = oArgumentTests[i];

                    //# Traverse the passed a(rguments), if a .test for the current oArgumentTests fails, reset oEntry and fall from the a(rgument)s loop
                    for (j = 0; j < a.length; j++) {
                        if (!oArgumentTests[i].tests[j](a[j])) {
                            oEntry = undefined;
                            break;
                        }
                    }

                    //# If all of the a(rgument)s passed the .tests we found our oEntry, so break from the oArgumentTests loop
                    if (oEntry) {
                        break;
                    }
                }

                //# If we found our oEntry above, .fn.call its .fn
                if (oEntry) {
                    oEntry.calls++;
                    return fnCall(oEntry.fn, this, a);
                }
                //# Else we were unable to find a matching oArgumentTests oEntry, so .fn.call our .default
                else {
                    return fnCall(oData.default, this, a);
                }
            } //# fnOverload
        ;

        //# 
        fnOverload.add = function (fn, a_vArgumentTests, sAlias) {
            var i,
                bValid = isFn(fn),
                iLen = (isArr(a_vArgumentTests) ? a_vArgumentTests.length : 0)
            ;

            //# 
            if (bValid) {
                //# Traverse the a_vArgumentTests, processinge each to ensure they are functions (or references to )
                for (i = 0; i < iLen; i++) {
                    if (!isFn(a_vArgumentTests[i])) {
                        bValid = _false;
                    }
                }
            }

            //# If the a_vArgumentTests are bValid, set the info into oData under the a_vArgumentTests's iLen
            if (bValid) {
                oData[iLen] = oData[iLen] || [];
                oData[iLen].push({
                    fn: fn,
                    tests: a_vArgumentTests,
                    calls: 0
                });

                //# 
                registerAlias(fnOverload, fn, sAlias);

                return fnOverload;
            }
            //# Else one of the passed arguments was not bValid, so throw the error
            else {
                throw "poly.overload: All tests must be functions or strings referencing `is.*`.";
            }
        }; //# overload*.add

        //# 
        fnOverload.list = function (iArgumentCount) {
            return (arguments.length > 0 ? oData[iArgumentCount] || [] : oData);
        }; //# overload*.list

        //# 
        a_fnOverloads.push(fnOverload);
        registerAlias(fnOverload, oData.default, "default");

        return fnOverload;
    } //# overload

    //# 
    overload.is = function (fnTarget) {
        return (a_fnOverloads.indexOf(fnTarget) > -1);
    } //# overload.is

    return overload;
}();

Применение:

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

var myOverloadedFn = overload(function(){ console.log("default", arguments) })
    .add(function(){ console.log("noArgs", arguments) }, [], "noArgs")
    .add(function(){ console.log("str", arguments) }, [function(s){ return typeof s === 'string' }], "str")
;

Единственный необязательный аргумент, который overload()определяет функцию «по умолчанию» для вызова, если подпись не может быть идентифицирована. Аргументы .add():

  1. fn: functionопределение перегрузки;
  2. a_vArgumentTests: ArrayИз functionй определяющих тестов для запуска на arguments. Каждый functionпринимает один аргумент и возвращает trueтвой в зависимости от того, является ли аргумент действительным;
  3. sAlias(Необязательно): stringопределение псевдонима для прямого доступа к функции перегрузки ( fn), например myOverloadedFn.noArgs(), вызовет эту функцию напрямую, избегая тестов аргументов на динамический полиморфизм.

Эта реализация на самом деле допускает не только традиционные перегрузки функций в качестве второго a_vArgumentTestsаргумента.add() практике определяет пользовательские типы. Таким образом, вы можете задавать аргументы не только по типу, но и по диапазонам, значениям или коллекциям значений!

Если вы посмотрите на 145 строк кода, overload()вы увидите, что каждая подпись классифицируется по количеству argumentsпереданных ей. Это сделано для того, чтобы мы ограничивали количество тестов, которые мы проводим. Я также отслеживаю количество звонков. С некоторым дополнительным кодом, массивы перегруженных функций могут быть пересортированы так, чтобы сначала тестировались более часто вызываемые функции, снова добавляя некоторую меру повышения производительности.

Теперь есть несколько предостережений ... Поскольку Javascript свободно набирается, вам нужно быть осторожным с тем, vArgumentTestsчто integerможно проверить как a floatи т. Д.

Версия JSCompress.com (1114 байтов, 744 байта в сжатом виде):

window.overload=function(){'use strict';function b(n){return'[object Function]'===m.call(n)}function c(n){return!!(n&&n===Object(n))}function d(n){return'[object Array]'===m.call(n)}function e(n){return Array.prototype.slice.call(n)}function g(n,p,q){if(q=d(q)?q:e(q),b(n))return n.apply(p||this,q)}function h(n,p,q){q&&!n[q]&&(n[q]=p)}function k(n){var p=b(n)?{default:n}:c(n)?n:{default:function(){throw'Overload not found for arguments: ['+e(arguments)+']'}},q=function(){var r,s,t,u=arguments,v=p[u.length]||[];for(s=0;s<v.length;s++){for(r=v[s],t=0;t<u.length;t++)if(!v[s].tests[t](u[t])){r=void 0;break}if(r)break}return r?(r.calls++,g(r.fn,this,u)):g(p.default,this,u)};return q.add=function(r,s,t){var u,v=b(r),w=d(s)?s.length:0;if(v)for(u=0;u<w;u++)b(s[u])||(v=_false);if(v)return p[w]=p[w]||[],p[w].push({fn:r,tests:s,calls:0}),h(q,r,t),q;throw'poly.overload: All tests must be functions or strings referencing `is.*`.'},q.list=function(r){return 0<arguments.length?p[r]||[]:p},l.push(q),h(q,p.default,'default'),q}var l=[],m=Object.prototype.toString;return k.is=function(n){return-1<l.indexOf(n)},k}();

2

Теперь вы можете выполнять перегрузку функций в ECMAScript 2018 без полизаполнений, проверки длины / типа переменной и т. Д., Просто используйте синтаксис расширения .

function foo(var1, var2, opts){
  // set default values for parameters
  const defaultOpts = {
    a: [1,2,3],
    b: true,
    c: 0.3289,
    d: "str",
  }
  // merge default and passed-in parameters
  // defaultOpts must go first!
  const mergedOpts = {...defaultOpts, ...opts};

  // you can now refer to parameters like b as mergedOpts.b,
  // or just assign mergedOpts.b to b
  console.log(mergedOpts.a);
  console.log(mergedOpts.b);
  console.log(mergedOpts.c);  
  console.log(mergedOpts.d);
}
// the parameters you passed in override the default ones
// all JS types are supported: primitives, objects, arrays, functions, etc.
let var1, var2="random var";
foo(var1, var2, {a: [1,2], d: "differentString"});

// parameter values inside foo:
//a: [1,2]
//b: true
//c: 0.3289
//d: "differentString"

Что такое распространенный синтаксис?

Свойства Rest / Spread для предложения ECMAScript (этап 4) добавляют свойства распространения к литералам объекта. Он копирует собственные перечисляемые свойства из предоставленного объекта в новый объект. Больше на MDN

Примечание: синтаксис распространения в объектных литералах не работает в Edge и IE, и это экспериментальная функция. увидеть совместимость браузера


2

Нечто подобное можно сделать для перегрузки функций.

function addCSS(el, prop, val) {
  return {
    2: function() {
      // when two arguments are set
      // now prop is an oject
      for (var i in prop) {
          el.style[i] = prop[i];
      }
    },
    3: function() {
      // when three arguments are set
      el.style[prop] = val;
    }
    }[arguments.length]();
}
// usage
var el = document.getElementById("demo");
addCSS(el, "color", "blue");
addCSS(el, {
    "backgroundColor": "black",
  "padding": "10px"
});

Источник


1

Это старый вопрос, но мне кажется, что нужна еще одна запись (хотя я сомневаюсь, что кто-нибудь прочтет ее). Использование выражений с немедленным вызовом функций (IIFE) может использоваться в сочетании с замыканиями и встроенными функциями для обеспечения перегрузки функций. Рассмотрим следующий (надуманный) пример:

var foo;

// original 'foo' definition
foo = function(a) {
  console.log("a: " + a);
}

// define 'foo' to accept two arguments
foo = (function() {
  // store a reference to the previous definition of 'foo'
  var old = foo;

  // use inline function so that you can refer to it internally
  return function newFoo(a,b) {

    // check that the arguments.length == the number of arguments 
    // defined for 'newFoo'
    if (arguments.length == newFoo.length) {
      console.log("a: " + a);
      console.log("b: " + b);

    // else if 'old' is a function, apply it to the arguments
    } else if (({}).toString.call(old) === '[object Function]') {
      old.apply(null, arguments);
    }
  }
})();

foo(1);
> a: 1
foo(1,2);
> a: 1
> b: 2
foo(1,2,3)
> a: 1

Короче говоря, использование IIFE создает локальную область, что позволяет нам определять приватную переменную oldдля хранения ссылки на первоначальное определение функции foo. Затем эта функция возвращает встроенную функцию, newFooкоторая регистрирует содержимое обоих двух аргументов, если ей передано ровно два аргумента, aи / bили вызывает oldфункцию if arguments.length !== 2. Этот шаблон может повторяться любое количество раз, чтобы наделить одну переменную несколькими различными функциональными определениями.


1

Я хотел бы поделиться полезным примером перегруженного подхода.

function Clear(control)
{
  var o = typeof control !== "undefined" ? control : document.body;
  var children = o.childNodes;
  while (o.childNodes.length > 0)
    o.removeChild(o.firstChild);
}

Использование: Очистить (); // очищает весь документ

Clear (myDiv); // Очищает панель, на которую ссылается myDiv


1

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

myFunction = function(a, b, c) {
     if (b === undefined && c === undefined ){
          // do x...
     }
     else {
          // do y...
     }
};

1
Сразу хочу отметить, что нетипизированный не означает «нет типов».
Hamdiakoguz

1

По состоянию на июль 2017 года, следующая техника была общей. Обратите внимание, что мы также можем выполнять проверку типов внутри функции.

function f(...rest){   // rest is an array
   console.log(rest.length);
   for (v of rest) if (typeof(v)=="number")console.log(v);
}
f(1,2,3);  // 3 1 2 3

1

Для вашего случая использования вот как я бы с этим справился ES6(так как это уже конец 2017 года):

const foo = (x, y, z) => {
  if (y && z) {
    // Do your foo(x, y, z); functionality
    return output;
  }
  // Do your foo(x); functionality
  return output;
}

Очевидно, вы можете адаптировать его для работы с любым количеством параметров и просто соответствующим образом изменить свои условные выражения.


1

в JS нет реальной перегрузки, в любом случае мы все еще можем имитировать перегрузку метода несколькими способами:

Метод № 1: использовать объект

function test(x,options){
  if("a" in options)doSomething();
  else if("b" in options)doSomethingElse();
}
test("ok",{a:1});
test("ok",{b:"string"});

Метод № 2: использовать остальные параметры

function test(x,...p){
 if(p[2])console.log("3 params passed"); //or if(typeof p[2]=="string")
else if (p[1])console.log("2 params passed");
else console.log("1 param passed");
}

Метод № 3: использовать неопределенный

function test(x, y, z){
 if(typeof(z)=="undefined")doSomething();
}

Метод № 4: проверка типа

function test(x){
 if(typeof(x)=="string")console.log("a string passed")
 else ...
}

1

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

function transformer(
    firstNumber = 1,
    secondNumber = new Date().getFullYear(),
    transform = function multiply(firstNumber, secondNumber) {
        return firstNumber * secondNumber;
    }
) {
    return transform(firstNumber, secondNumber);
}

console.info(transformer());
console.info(transformer(8));
console.info(transformer(2, 6));
console.info(transformer(undefined, 65));

function add(firstNumber, secondNumber) {
    return firstNumber + secondNumber;
}
console.info(transformer(undefined, undefined, add));
console.info(transformer(3, undefined, add));

Результаты за (к 2020 году):

2020
16160
12
65
2021
2023

Дополнительная информация: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters


0

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

  1. Использование разных имен в первую очередь

С небольшой, но важной подсказкой имена должны отличаться для компьютера, но не для вас. Назовите перегруженные функции, такие как: func, func1, func2.


Я собирался попробовать перегрузку, но решил просто использовать разные имена, например, getDeviceInfoByID и getDeviceInfoByType ...
CramerTV
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.