Вопрос по javascript – Как перегрузить функции в JavaScript?

86

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

function myFunc(){
 //code
}

function myFunc(overloaded){
 //other code
}

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

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

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

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

Тогда вы думаете о неправильном JavaScript; если у вас есть проблемы с перегрузками и областью видимости, у вас, вероятно, должно быть другое имя метода, так как кажется, что он выполняет другую работу joshcomley
@ joshcomley - Перегрузки и область требуют кода для обработки. Я просто пытаюсь убедиться, что код, который я использую, максимально эффективен. Обработка областей с обходными путями - это хорошо, но я предпочитаю хотя бы попытаться использовать лучшие методы. Travis J

Ваш Ответ

13   ответов
3

https: //github.com/jrf0110/leFun

  "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"
3

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

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

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

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

105

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

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

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

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

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

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

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

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

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

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

Давайте посмотрим на jQuery'sobj.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);

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

// 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's$.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.

Так что я должен просто придерживаться адаптируемой функции и принять это как лучшую практику для реализации функции фасада перегрузки? Travis J
@ TravisJ - да, адаптируемая функция - это способ перегрузки в Javascript. Другой вариант - использовать отдельно названную функцию с другими аргументами. Если две реализации не имеют ничего общего, то любая из них является приемлемой практикой. Если две реализации по сути делают одно и то же, но просто начинают с разных аргументов, то я думаю, что это делает ваш интерфейс более компактным, чтобы использовать адаптируемую функцию, и это дает вам возможность обмениваться кодом между двумя связанными реализациями. jfriend00
Добавлены некоторые примеры кода, объясненные как на английском, так и в реальном коде. jfriend00
Я получаю сообщение об ошибке, неожиданный токен '=' в последней версии Safari при попытке этогоresetProgressBar: function(display_errors, lockout = false). Nick
@ Ник - За это ES6 совместимость, таблица типов, значения параметров по умолчанию должны работать в Safari 10 и более поздних версиях. За эта страница MDN, в Safari написано «нет поддержки». У меня нет Mac, поэтому я сам не могу его проверить. Похоже, что это, как правило, одна из последних реализованных функций ES6 во многих браузерах. Например, он пока не выглядит как в iOS9. jfriend00
30

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

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

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, хотя это увеличивает основную функцию. Типичным примером того, где я бы использовал эту технику, был бы метод доступа / мутатора:

function Foo() {} //constructor
Foo.prototype = {
    bar: function (val) {
        switch (arguments.length) {
        case 0:
            return this._bar;
        case 1:
            this._bar = val;
            return this;
        }
    }
}
Это хороший пример альтернативы Travis J
Хорошо, мне нравится эта вторая техника Nick Rolando
5

как это поддерживается в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 заявления

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

4

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

// 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 :)
3

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

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;
};
Функция для обработки входов, очень умная. Travis J
2

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

}

Во - пример использования объекта параметров

2

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

<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>
2
Нет проблем с перегрузкой в JS, ПБ, как поддерживать чистый код при перегрузке функции?

вперед иметь чистый код, основанный на двух вещах:

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

Тип аргументов (при вызове функции)

  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 ....   
0

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

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;
};
0

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

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

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

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

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

Похожие вопросы