Как перегружать функции в javascript?

Классический (не-js) подход к перегрузке:

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

Javascript не позволяет определять более одной функции с тем же именем. Таким образом, такие вещи появляются:

function myFunc(options){
 if(options["overloaded"]){
  //code
 }
}

Есть ли лучший способ для перегрузки функций в javascript, кроме передачи объекта с перегрузками в нем?

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

Ответ 1

Существует несколько аспектов перегрузки аргументов в Javascript:

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

  • Аргументы по умолчанию. Вы можете определить значение по умолчанию для аргумента, если оно не передано.

  • Именованные аргументы - порядок аргументов становится неуместным, и вы просто указываете, какие аргументы вы хотите передать функции.

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

Переменные аргументы

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

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

При реализации этих типов перегрузок вы можете использовать несколько различных методов:

  • Вы можете проверить наличие какого-либо данного аргумента, проверив, указано ли значение имени объявленного аргумента undefined.
  • Вы можете проверить общее количество или аргументы с помощью arguments.length.
  • Вы можете проверить тип любого аргумента.
  • Для переменных чисел аргументов вы можете использовать псевдо-массив arguments для доступа к любому заданному аргументу с помощью arguments[i].

Вот несколько примеров:

Посмотрите на метод jQuery obj.data(). Он поддерживает четыре различные формы использования:

obj.data("key");
obj.data("key", value);
obj.data();
obj.data(object);

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

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

// get the data element associated with a particular key value
obj.data("key");

Если первый аргумент, переданный в .data(), является строкой, а второй аргумент undefined, то вызывающий должен использовать эту форму.


// set the value associated with a particular key
obj.data("key", value);

Если второй аргумент не является undefined, тогда установите значение определенного ключа.


// get all keys/values
obj.data();

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


// set all keys/values from the passed in object
obj.data(object);

Если тип первого аргумента является простым объектом, тогда установите все ключи/значения из этого объекта.


Здесь вы можете объединить все в одном наборе логики JavaScript:

 // method declaration for .data()
 data: function(key, value) {
     if (arguments.length === 0) {
         // .data()
         // no args passed, return all keys/values in an object
     } else if (typeof key === "string") {
         // first arg is a string, look at type of second arg
         if (typeof value !== "undefined") {
             // .data("key", value)
             // set the value for a particular key
         } else {
             // .data("key")
             // retrieve a value for a key
         }
     } else if (typeof key === "object") {
         // .data(object)
         // set all key/value pairs from this object
     } else {
         // unsupported arguments passed
     }
 },

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

Например, если у вас есть функция, которая принимает три строковых аргумента:

obj.query("firstArg", "secondArg", "thirdArg");

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

obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");

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

obj.query("firstArg", null, "thirdArg");

Здесь пример jQuery необязательных аргументов. оба аргумента являются необязательными и принимают значения по умолчанию, если не переданы:

clone: function( dataAndEvents, deepDataAndEvents ) {
    dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
    deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

    return this.map( function () {
        return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
    });
},

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

html: function( value ) {
    if ( value === undefined ) {
        return this[0] && this[0].nodeType === 1 ?
            this[0].innerHTML.replace(rinlinejQuery, "") :
            null;

    // See if we can take a shortcut and just use innerHTML
    } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
        (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
        !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

        value = value.replace(rxhtmlTag, "<$1></$2>");

        try {
            for ( var i = 0, l = this.length; i < l; i++ ) {
                // Remove element nodes and prevent memory leaks
                if ( this[i].nodeType === 1 ) {
                    jQuery.cleanData( this[i].getElementsByTagName("*") );
                    this[i].innerHTML = value;
                }
            }

        // If using innerHTML throws an exception, use the fallback method
        } catch(e) {
            this.empty().append( value );
        }

    } else if ( jQuery.isFunction( value ) ) {
        this.each(function(i){
            var self = jQuery( this );

            self.html( value.call(this, i, self.html()) );
        });

    } else {
        this.empty().append( value );
    }

    return this;
},

Именованные аргументы

Другие языки (например, Python) позволяют передавать именованные аргументы как средство передачи только некоторых аргументов и сделать аргументы независимыми от порядка, в котором они передаются. Javascript напрямую не поддерживает функцию именованных аргументов. Шаблон проектирования, который обычно используется на его месте, состоит в том, чтобы передать карту свойств/значений. Это можно сделать, передав объект со свойствами и значениями или в ES6 и выше, вы можете фактически передать сам объект Map.

Вот простой пример ES5:

jQuery $.ajax() принимает форму использования, где вы просто передаете ему единственный параметр, который является обычным объектом Javascript со свойствами и значениями. Какие свойства, которые вы передаете, определяют, какие аргументы/параметры передаются на вызов ajax. Некоторые могут потребоваться, многие из них являются необязательными. Так как они являются свойствами объекта, то нет конкретного порядка. На самом деле существует более 30 различных свойств, которые могут быть переданы этому объекту, требуется только один (URL).

Вот пример:

$.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) {
    // process result here
});

Внутри реализации $.ajax() он может просто запросить, какие свойства были переданы входящему объекту, и использовать их как именованные аргументы. Это можно сделать либо с помощью for (prop in obj), либо путем ввода всех свойств в массив с помощью Object.keys(obj), а затем итерации этого массива.

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

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

Вот один пример из этой статьи:

function selectEntries({ start=0, end=-1, step=1 } = {}) {
    ···
};

Это создает свойства и значения по умолчанию для свойств start, end и step объекта, переданного функции selectEntries().

Значения по умолчанию для аргументов функции

В ES6 Javascript добавляет встроенную поддержку языка для значений по умолчанию для аргументов.

Например:

function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5

Дальнейшее описание способов, которыми это можно использовать здесь, в MDN.

Ответ 2

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

Один из самых простых простых методов включает простой переключатель:

function foo(a, b) {
    switch (arguments.length) {
    case 0:
        //do basic code
        break;
    case 1:
        //do code with `a`
        break;
    case 2:
    default:
        //do code with `a` & `b`
        break;
    }
}

Более элегантным методом было бы использование массива (или объекта, если вы не делаете перегрузки для каждого аргумента):

fooArr = [
    function () {
    },
    function (a) {
    },
    function (a,b) {
    }
];
function foo(a, b) {
    return fooArr[arguments.length](a, b);
}

Этот предыдущий пример не очень изящный, любой может изменить fooArr, и он потерпит неудачу, если кто-то передаст более чем 2 аргументам в foo, поэтому лучшей формой будет использование шаблона модуля и несколько проверки:

var foo = (function () {
    var fns;
    fns = [
        function () {
        },
        function (a) {
        },
        function (a, b) {
        }
    ];
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = foo.length;
        }
        return fns[fnIndex].call(this, a, b);
    }
    return foo;
}());

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

var foo = (function () {
    var fns;
    fns = {};
    fns[0] = function () {
    };
    fns[1] = function (a) {
    };
    fns[2] = function (a, b) {
    };
    fns.params = function (a, b /*, params */) {
    };
    function foo(a, b) {
        var fnIndex;
        fnIndex = arguments.length;
        if (fnIndex > foo.length) {
            fnIndex = 'params';
        }
        return fns[fnIndex].apply(this, Array.prototype.slice.call(arguments));
    }
    return foo;
}());

Мои личные предпочтения имеют тенденцию быть switch, хотя он набирает основную функцию. Общим примером того, где я буду использовать этот метод, будет метод accessor/mutator:

function Foo() {} //constructor
Foo.prototype = {
    bar: function (val) {
        switch (arguments.length) {
        case 0:
            return this._bar;
        case 1:
            this._bar = val;
            return this;
        }
    }
}

Ответ 3

Я использую немного другой подход перегрузки, основанный на числе аргументов. Однако я считаю, что подход Джона Фосетта тоже хорош. Вот пример, код, основанный на объяснениях Джона Ресига (jQuery Author).

// o = existing object, n = function name, f = function.
    function overload(o, n, f){
        var old = o[n];
        o[n] = function(){
            if(f.length == arguments.length){
                return f.apply(this, arguments);
            }
            else if(typeof o == 'function'){
                return old.apply(this, arguments);
            }
        };
    }

удобство и простота использования:

var obj = {};
overload(obj, 'function_name', function(){ /* what we will do if no args passed? */});
overload(obj, 'function_name', function(first){ /* what we will do if 1 arg passed? */});
overload(obj, 'function_name', function(first, second){ /* what we will do if 2 args passed? */});
overload(obj, 'function_name', function(first,second,third){ /* what we will do if 3 args passed? */});
//... etc :)

Ответ 4

Вы не можете перегружать метод в строгом смысле. Не так, как поддерживается в java или c#.

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

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

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

function foo(x)
{
} 

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

fooNew(x,y,z)
{
}

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

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

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

(подробнее)

Ответ 5

В javascript вы можете реализовать функцию только один раз и вызвать функцию без параметров myFunc() Затем вы проверяете, есть ли опции undefined '

function myFunc(options){
 if(typeof options != 'undefined'){
  //code
 }
}

Ответ 6

https://github.com/jrf0110/leFunc

var getItems = leFunc({
  "string": function(id){
    // Do something
  },
  "string,object": function(id, options){
    // Do something else
  },
  "string,object,function": function(id, options, callback){
    // Do something different
    callback();
  },
  "object,string,function": function(options, message, callback){
    // Do something ca-raaaaazzzy
    callback();
  }
});

getItems("123abc"); // Calls the first function - "string"
getItems("123abc", {poop: true}); // Calls the second function - "string,object"
getItems("123abc", {butt: true}, function(){}); // Calls the third function - "string,object,function"
getItems({butt: true}, "What what?" function(){}); // Calls the fourth function - "object,string,function"

Ответ 7

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

var out = def({
    'int': function(a) {
        alert('Here is int '+a);
    },

    'float': function(a) {
        alert('Here is float '+a);
    },

    'string': function(a) {
        alert('Here is string '+a);
    },

    'int,string': function(a, b) {
        alert('Here is an int '+a+' and a string '+b);
    },
    'default': function(obj) {
        alert('Here is some other value '+ obj);
    }

});

out('ten');
out(1);
out(2, 'robot');
out(2.5);
out(true);

Используемые методы:

var def = function(functions, parent) {
 return function() {
    var types = [];
    var args = [];
    eachArg(arguments, function(i, elem) {
        args.push(elem);
        types.push(whatis(elem));
    });
    if(functions.hasOwnProperty(types.join())) {
        return functions[types.join()].apply(parent, args);
    } else {
        if (typeof functions === 'function')
            return functions.apply(parent, args);
        if (functions.hasOwnProperty('default'))
            return functions['default'].apply(parent, args);        
    }
  };
};

var eachArg = function(args, fn) {
 var i = 0;
 while (args.hasOwnProperty(i)) {
    if(fn !== undefined)
        fn(i, args[i]);
    i++;
 }
 return i-1;
};

var whatis = function(val) {

 if(val === undefined)
    return 'undefined';
 if(val === null)
    return 'null';

 var type = typeof val;

 if(type === 'object') {
    if(val.hasOwnProperty('length') && val.hasOwnProperty('push'))
        return 'array';
    if(val.hasOwnProperty('getDate') && val.hasOwnProperty('toLocaleTimeString'))
        return 'date';
    if(val.hasOwnProperty('toExponential'))
        type = 'number';
    if(val.hasOwnProperty('substring') && val.hasOwnProperty('length'))
        return 'string';
 }

 if(type === 'number') {
    if(val.toString().indexOf('.') > 0)
        return 'float';
    else
        return 'int';
 }

 return type;
};

Ответ 8

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

var notify = new bob.fn.overloadFunction([ 
    { 
        condition: function(msg) { return bob.utils.isString(msg); }, 
        overload: function(msg) { 
            console.log(msg); 
        } 
    }, 
    { 
        condition: function(bSayHello) { return bob.utils.isBoolean(bSayHello); }, 
        overload: function(bSayHello, msg) { 
            msg = bSayHello ? 'Hello: ' + msg : msg; 
            console.log(msg); 
        } 
    } 
]); 

Однако конечный результат довольно чистый:

notify('Simple sentence.'); 
// Output: 
// Simple sentence. 
notify(true, 'Greeting sentence.'); 
// Output: 
// Hello: Greeting sentence. 
notify(123); 
// JavaScript Error: 
// "No matching overload found." 

Ответ 9

Проверьте это:

http://www.codeproject.com/Articles/688869/Overloading-JavaScript-Functions

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

Ответ 10

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

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 + "}";

}

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

Ответ 11

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

<script > 
//Main function to add the methods
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);
  };
}


  var ninjas = {
   values: ["Dean Edwards", "Sam Stephenson", "Alex Russell"]
};

//Here we declare the first function with no arguments passed
  addMethod(ninjas, "find", function(){
    return this.values;
});

//Second function with one argument
  addMethod(ninjas, "find", function(name){
    var ret = [];
    for (var i = 0; i < this.values.length; i++)
      if (this.values[i].indexOf(name) == 0)
        ret.push(this.values[i]);
    return ret;
  });

//Third function with two arguments
  addMethod(ninjas, "find", function(first, last){
    var ret = [];
    for (var i = 0; i < this.values.length; i++)
      if (this.values[i] == (first + " " + last))
        ret.push(this.values[i]);
    return ret;
  });


//Now you can do:
ninjas.find();
ninjas.find("Sam");
ninjas.find("Dean", "Edwards")
</script>

Ответ 12

Нет проблемы с перегрузкой в ​​JS, pb как сохранить чистый код при перегрузке?

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

  • Количество аргументов (при вызове функции).
  • Тип аргументов (при вызове функции)

      function myFunc(){
          return window['myFunc_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);
       }
    
        /** one argument & this argument is string */
      function myFunc_1_string(){
    
      }
       //------------
       /** one argument & this argument is object */
      function myFunc_1_object(){
    
      }
      //----------
      /** two arguments & those arguments are both string */
      function myFunc_2_string_string(){
    
      }
       //--------
      /** Three arguments & those arguments are : id(number),name(string), callback(function) */
      function myFunc_3_number_string_function(){
                let args=arguments;
                  new Person(args[0],args[1]).onReady(args[3]);
      }
    
       //--- And so on ....   
    

Ответ 13

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

var doSomething = function() {
    var foo;
    var bar;
};

doSomething.withArgSet1 = function(arg0, arg1) {
    var obj = new doSomething();
    // do something the first way
    return obj;
};

doSomething.withArgSet2 = function(arg2, arg3) {
    var obj = new doSomething();
    // do something the second way
    return obj;
};

Ответ 14

То, что вы пытаетесь достичь, лучше всего использовать с помощью локальной переменной arguments.

function foo() {
    if (arguments.length === 0) {
        //do something
    }
    if (arguments.length === 1) {
        //do something else
    }
}

foo(); //do something
foo('one'); //do something else

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