Pull to refresh

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

Reading time 4 min
Views 53K
Как известно, в JavaScript нельзя создать несколько функций, различающихся только списком параметров: последняя созданная перезапишет предыдущие. Про различие на уровне типов параметров говорить не приходится вообще. Обычно, если программист хочет создать функцию с множественным интерфейсом, он пишет что-то вроде такого:
  1. // getRectangleArea(x1, y1, x2, y2) или
  2. // getRectangleArea(width, height)
  3. function getRectangleArea(x1, y1, x2, y2) {
  4.   if(arguments.length==2) return x1*y1;
  5.   return (x2-x1)*(y2-y1);
  6. }
* This source code was highlighted with Source Code Highlighter.

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

Во-первых, хотелось бы написать отдельные функции для каждого набора параметров примерно так:
  1. function getRectangleArea(width, height) {
  2.   return width * height;
  3. }
  4.  
  5. function getRectangleArea(x1, y1, x2, y2) {
  6.   return (x2-x1)*(y2-y1);
  7. }
* This source code was highlighted with Source Code Highlighter.

Так код красивее и логичнее, можно в каждой функции дать параметрам говорящие имена. Одна беда: он не работает. Но мы можем написать генератор функций первого типа, подав ему на вход набор подфункций, как во втором примере. Пусть для начала у нас все подфункции имеют разное количество параметров. Количество параметров функции можем узнать по functionName.length, а количество переданных аргументов — по arguments.length. Вспомним также великую штуку apply и напишем:
  1. function polymorph() {
  2.   var len2func = [];
  3.   for(var i=0; i<arguments.length; i++)
  4.     if(typeof(arguments[i]) == "function")
  5.       len2func[arguments[i].length] = arguments[i];
  6.   return function() {
  7.     return len2func[arguments.length].apply(this, arguments);
  8.   }
  9. }
* This source code was highlighted with Source Code Highlighter.

Функция polymorph принимает в качестве аргументов набор подфункций с разным количеством параметров, заносит их в массив len2func, индекс которого — число параметров, и возвращает функцию-замыкание, которая в зависимости от числа аргументов вызывает ту или иную подфункцию. Пользоваться так:
  1. var getRectangleArea2 = polymorph(
  2.   function(width, height) {
  3.     return width * height;
  4.   },
  5.   function(x1, y1, x2, y2) {
  6.     return (x2-x1)*(y2-y1);
  7.   }
  8. );
* This source code was highlighted with Source Code Highlighter.

Теперь getRectangleArea2 — полный аналог getRectangleArea, однако код стал гораздо прозрачнее, и теперь уже не требуется комментарий: способы использования очевидны.

Ситуация становится сложнее, если мы хотим поддерживать функции с одинаковым количеством параметров, отличающихся типами. Однако типы можно контролировать с помощью typeof и instanceof, поэтому несложно расширить функцию polymorph() и для этих случаев. Типы параметров будем задавать в специальном объекте перед функцией:
  1. var PolyFunc = polymorph(
  2.     function(a,b,c) {
  3.         return "Three arguments version -- any types";
  4.     },
  5.     
  6.     {i: Number, str: String},
  7.     function(i,str) {
  8.         return "Number and string passed";
  9.     },
  10.     
  11.     {re: RegExp},
  12.     function(re,a) {
  13.         return "RegExp and something else passed";
  14.     },
  15.     
  16.     {f: Function, b: Boolean},
  17.     function(f,b) {
  18.         return "Function and boolean passed";
  19.     },
  20.     
  21.     {f: Function, i: Number},
  22.     function(f,i) {
  23.         return "Function and number passed";
  24.     }
  25. );
  26.  
  27. alert(PolyFunc(1,2,3)); // "Three arguments version -- any types"
  28. alert(PolyFunc(1,"qq")); // "Number and string passed"
  29. alert(PolyFunc(function() {}, true)); // "Function and boolean passed"
  30. alert(PolyFunc(function() {}, 1)); // "Function and number passed"
  31. alert(PolyFunc(/a/, 1)); // "RegExp and something else passed"
  32. alert(PolyFunc(/a/, "str")); // "RegExp and something else passed"
* This source code was highlighted with Source Code Highlighter.

Всё это дело я выложил в Google code и буду рад, если кому-нибудь пригодится. Перегружать можно создать не только обычные функции, но и методы, и конструкторы объектов (примеры есть по ссылке), причём их можно определять через друг друга (вызывая свою версию с другим набором параметров). В качестве типов параметров можно использовать как встроенные, так и пользовательские. Есть возможность модифицировать уже созданную полиморфную функцию, добавив или заменив какие-то подфункции.

За красоту, к сожалению, приходится платить и как всегда — быстродействием. Оверхед на вызов функции на моём Core2DUO @ 2.66GHz составил в FF3.6, IE8, Opera10 и Safari около 1-10 мкс (IE самый медленный, затем FF, потом Opera и Safari). Chrome демонстрирует наилучший результат в диапазоне 0.3-1 мкс, обгоняя FF в 10 раз. Для функций, которые сами по себе быстрые и вызываются очень часто, используйте осторожно.
Tags:
Hubs:
+92
Comments 59
Comments Comments 59

Articles