Перегрузка функций в Javascript - лучшие практики - PullRequest
711 голосов
/ 19 января 2009

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

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

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

Ответы [ 33 ]

5 голосов
/ 09 января 2014

Так как в 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 голосов
/ 24 августа 2017

Другим способом решения этой проблемы является использование специальной переменной: 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;
}
4 голосов
/ 24 августа 2017

Введение

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

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 голосов
/ 01 декабря 2011

Нет способа перегрузить функцию в 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!!");
     }
}

Удачи!

3 голосов
/ 12 марта 2012

проверить это. Это очень круто. 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
2 голосов
/ 19 февраля 2014

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

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 голосов
/ 25 августа 2014

Вы можете использовать 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 голосов
/ 02 марта 2019

Теперь вы можете выполнять перегрузку функций в 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 голосов
/ 27 декабря 2017

Перегрузка функций через динамический полиморфизм в 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 s, определяющих тесты для запуска на arguments. Каждый function принимает один аргумент и возвращает true твой в зависимости от того, является ли аргумент действительным;
  3. sAlias (Необязательно): string определение псевдонима для прямого доступа к функции перегрузки (fn), например, myOverloadedFn.noArgs() будет вызывать эту функцию напрямую, избегая проверки аргументов на динамический полиморфизм.

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

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

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

Версия JSCompress.com (1114 байт, 744 байт в формате g-zip):

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 голосов
/ 19 июля 2016

Шаблон пересылки => наилучшая практика по перегрузке 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 ....   
...