Перевантаження функцій у Javascript - кращі практики


783

Який найкращий спосіб (і) підробити функцію перевантаження функцій Javascript?

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

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

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

1
Це закрито, але я виконую наступне: this.selectBy = {instance: selectByInstance, // Текст функції: selectByText, // Значення функції: selectByValue // Функція};
В'язень ЗЕРО

У моїй відповіді показано, як робити перевантаження функції часу роботи, у неї швидкість покарання, і я б не радив це робити, щоб обійти специфікацію Javascript. Перевантаження функцій - це справді завдання часу компіляції, я даю відповідь лише в академічних цілях і залишаю її на власний розсуд щодо того, використовувати чи ні в коді.
Keldon Alleyne

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

Відповіді:


602

Найкращий спосіб зробити перевантаження функцій параметрами - не перевіряти довжину аргументу або типи; перевірка типів просто зробить ваш код повільним, і ви отримаєте задоволення від масивів, нулів, об’єктів тощо.

Більшість розробників робить те, що останній аргумент їх методів є об'єктом об'єкта. Цей об’єкт вміщує все, що завгодно.

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" використовуються / посилаються?
Moe Howard

24
Мо // Могло бути так; if(opts['test']) //if test param exists, do something.. if(opts['bar']) //if bar param exists, do something
Декард

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 #. Параметри, які не вказані, є "undefined" у javascript, що оцінює значення false у операторі if. Однак визначення функції не передає інформацію про те, що p2 і p3 необов'язкові. Якщо вам потрібно багато перевантажень, jQuery вирішив використовувати об'єкт як параметр, наприклад, jQuery.ajax (параметри). Я погоджуюся з ними, що це найпотужніший і чітко задокументований підхід до перевантаження, але мені рідко потрібно більше одного-двох швидких необов'язкових параметрів.

EDIT: змінено ІФ-тест за пропозицією Яна


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
Функція Джона Резіга
Терранс

@Терранс: Мені також подобається метод Резіга. Це працює як шарм. Мені просто потрібно знайти спосіб створити тест для перевірки випадків використання.
chrisvillanueva

"Ця функція не змінить світ, але вона коротка, стисла і використовує незрозумілу функцію JavaScript - тому вона виграє в моїй книзі." :-)
Фріріх Раабе

67

Правильна відповідь: В JAVASCRIPT НЕ ПОВЕРНУТЬСЯ.

Перевірка / перемикання всередині функції не ЗАПОВНЕНЕ.

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

Наприклад, doTask () і doTask (об'єкт O) є перевантаженими методами. Для виклику останнього, об'єкт повинен бути переданий як параметр, тоді як перший не потребує параметра, і викликається з порожнім полем параметра. Поширеною помилкою було б призначити об'єкту значення за замовчуванням у другому методі, що призведе до неоднозначної помилки виклику, оскільки компілятор не знатиме, який із двох методів використовувати.

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

Всі запропоновані реалізації є чудовими, але правда слід сказати, що немає вродженої реалізації для JavaScript.


4
нарешті нормальна відповідь! В JAVASCRIPT НЕ ПОВЕРНЕНО.
Razvan Tudorica

4
ОП попросили спосіб підробити перевантаження.
Аматорські ігри

Як я вже говорив раніше, ми тут навчаємо людей, ми не просто даємо відповіді на них, не перевіряючи, що те, що вони просять, є правильним.
Марко

27

Є два способи, як можна краще підійти до цього:

  1. Передайте словник (асоціативний масив), якщо ви хочете залишити багато гнучкості

  2. Візьміть об’єкт як аргумент і використовуйте спадкування на основі прототипу, щоб додати гнучкість.


1
це було моєю початковою думкою, однак, якщо функція, яку ви створюєте, буде використовуватися в бібліотеці чи іншими особами, перерахування цих значень може бути корисним
roberthuttinger

19

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

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

Edit (2018) : Оскільки це було написано у 2011 році, швидкість прямих викликів методів значно зросла, тоді як швидкість перевантажених методів не зробила.

Я б не рекомендував цей підхід, але це варте продуманого вправи, щоб подумати про те, як можна вирішити такі типи проблем.


Ось орієнтир різних підходів - https://jsperf.com/function-overloading . Це показує , що функція перевантаження ( з урахуванням типів до уваги) може бути близько 13 разів повільніше в Google V8 в Chrome від 16,0 (бета) .

Крім передачі об'єкта (тобто {x: 0, y: 0}), можна також застосовувати підхід С, коли це доречно, називаючи методи відповідно. Наприклад, Vector.AddVector (вектор), Vector.AddIntegers (x, y, z, ...) та Vector.AddArray (integerArray). Ви можете переглянути бібліотеки С, наприклад OpenGL для іменування натхнення.

Редагувати : Я додав орієнтир для передачі об'єкта та тестування об'єкта за допомогою 'param' in argі arg.hasOwnProperty('param'), і перевантаження функцій набагато швидше, ніж передача об'єкта та перевірка наявності властивостей (принаймні, у цьому орієнтирі).

З точки зору проектування, перевантаження функцій є дійсним або логічним лише в тому випадку, якщо перевантажені параметри відповідають одній дії. Таким чином, очевидно, що повинен бути базовий метод, який стосується лише конкретних деталей, інакше це може вказувати на невідповідний вибір дизайну. Таким чином, можна також вирішити використання перевантаження функцій шляхом перетворення даних у відповідний об'єкт. Звичайно, слід враховувати масштаб проблеми, оскільки немає необхідності в розробці складних конструкцій, якщо ваш намір полягає лише в тому, щоб надрукувати ім'я, але для дизайну рамок і бібліотек така думка виправдана.

Мій приклад виходить із реалізації прямокутника - звідси згадка про розмірність та точку. Можливо, Прямокутник міг би додати 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)
{
}

а потім змінити 1-ю функцію так:

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: Я додав кілька сподіваюсь корисних коментарів.
chessweb

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
Заради Бога! Використовуйте оператор переключення!
jedmao

8
Крім того, якщо ви наполягаєте на тому, щоб не використовувати комутатор, вам слід зателефонувати лише один раз. var type = typeof param; if (type === 'string') ...
Вальтер Стабош

+1, щоб прокоментувати "===". Інша перевага оператора переключення, якщо (... == ...) - це безпека типу.
Натан Купер

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

Ще один спосіб наблизитись до цього - за допомогою спеціальної змінної: аргументи , це реалізація:

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/ Trick 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())буду виконувати різні функції. Але я мушу зазначити, що це брудна хакерська робота, оскільки перевантаження методу - це справді завдання часу компіляції, а не час роботи.
Keldon Alleyne

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');

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

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" від John Resig. За допомогою цього методу ви можете "перевантажувати" методи на основі підрахунку аргументів.

// 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може бути перевірено як і floatт.д.

Версія JSCompress.com (1114 байт, 744 байтів g-zipped):

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 без полів заповнення, перевірки довжини / типу var тощо. Просто використовуйте синтаксис розповсюдження .

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"

Що таке синтаксис поширення?

Властивості відпочинку / розповсюдження для пропозиції 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функцію, якщо 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);
}

Використання: очистити (); // Очищає весь документ

Ясно (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.