Как известно, в JavaScript нельзя создать несколько функций, различающихся только списком параметров: последняя созданная перезапишет предыдущие. Про различие на уровне типов параметров говорить не приходится вообще. Обычно, если программист хочет создать функцию с множественным интерфейсом, он пишет что-то вроде такого:
Пока пример выглядит не очень страшно, однако интерфейсов может со временем стать заметно больше, тогда функция станет плохочитаема. Посмотрим, что можно с этим сделать.
Во-первых, хотелось бы написать отдельные функции для каждого набора параметров примерно так:
Так код красивее и логичнее, можно в каждой функции дать параметрам говорящие имена. Одна беда: он не работает. Но мы можем написать генератор функций первого типа, подав ему на вход набор подфункций, как во втором примере. Пусть для начала у нас все подфункции имеют разное количество параметров. Количество параметров функции можем узнать по
Функция
Теперь
Ситуация становится сложнее, если мы хотим поддерживать функции с одинаковым количеством параметров, отличающихся типами. Однако типы можно контролировать с помощью
Всё это дело я выложил в Google code и буду рад, если кому-нибудь пригодится. Перегружать можно создать не только обычные функции, но и методы, и конструкторы объектов (примеры есть по ссылке), причём их можно определять через друг друга (вызывая свою версию с другим набором параметров). В качестве типов параметров можно использовать как встроенные, так и пользовательские. Есть возможность модифицировать уже созданную полиморфную функцию, добавив или заменив какие-то подфункции.
За красоту, к сожалению, приходится платить и как всегда — быстродействием. Оверхед на вызов функции на моём Core2DUO @ 2.66GHz составил в FF3.6, IE8, Opera10 и Safari около 1-10 мкс (IE самый медленный, затем FF, потом Opera и Safari). Chrome демонстрирует наилучший результат в диапазоне 0.3-1 мкс, обгоняя FF в 10 раз. Для функций, которые сами по себе быстрые и вызываются очень часто, используйте осторожно.
- // getRectangleArea(x1, y1, x2, y2) или
- // getRectangleArea(width, height)
- function getRectangleArea(x1, y1, x2, y2) {
- if(arguments.length==2) return x1*y1;
- return (x2-x1)*(y2-y1);
- }
* This source code was highlighted with Source Code Highlighter.
Пока пример выглядит не очень страшно, однако интерфейсов может со временем стать заметно больше, тогда функция станет плохочитаема. Посмотрим, что можно с этим сделать.
Во-первых, хотелось бы написать отдельные функции для каждого набора параметров примерно так:
- function getRectangleArea(width, height) {
- return width * height;
- }
-
- function getRectangleArea(x1, y1, x2, y2) {
- return (x2-x1)*(y2-y1);
- }
* This source code was highlighted with Source Code Highlighter.
Так код красивее и логичнее, можно в каждой функции дать параметрам говорящие имена. Одна беда: он не работает. Но мы можем написать генератор функций первого типа, подав ему на вход набор подфункций, как во втором примере. Пусть для начала у нас все подфункции имеют разное количество параметров. Количество параметров функции можем узнать по
functionName.length
, а количество переданных аргументов — по arguments.length
. Вспомним также великую штуку apply
и напишем:
- function polymorph() {
- var len2func = [];
- for(var i=0; i<arguments.length; i++)
- if(typeof(arguments[i]) == "function")
- len2func[arguments[i].length] = arguments[i];
- return function() {
- return len2func[arguments.length].apply(this, arguments);
- }
- }
* This source code was highlighted with Source Code Highlighter.
Функция
polymorph
принимает в качестве аргументов набор подфункций с разным количеством параметров, заносит их в массив len2func
, индекс которого — число параметров, и возвращает функцию-замыкание, которая в зависимости от числа аргументов вызывает ту или иную подфункцию. Пользоваться так:
- var getRectangleArea2 = polymorph(
- function(width, height) {
- return width * height;
- },
- function(x1, y1, x2, y2) {
- return (x2-x1)*(y2-y1);
- }
- );
* This source code was highlighted with Source Code Highlighter.
Теперь
getRectangleArea2
— полный аналог getRectangleArea
, однако код стал гораздо прозрачнее, и теперь уже не требуется комментарий: способы использования очевидны.Ситуация становится сложнее, если мы хотим поддерживать функции с одинаковым количеством параметров, отличающихся типами. Однако типы можно контролировать с помощью
typeof
и instanceof
, поэтому несложно расширить функцию polymorph()
и для этих случаев. Типы параметров будем задавать в специальном объекте перед функцией:
- var PolyFunc = polymorph(
- function(a,b,c) {
- return "Three arguments version -- any types";
- },
-
- {i: Number, str: String},
- function(i,str) {
- return "Number and string passed";
- },
-
- {re: RegExp},
- function(re,a) {
- return "RegExp and something else passed";
- },
-
- {f: Function, b: Boolean},
- function(f,b) {
- return "Function and boolean passed";
- },
-
- {f: Function, i: Number},
- function(f,i) {
- return "Function and number passed";
- }
- );
-
- alert(PolyFunc(1,2,3)); // "Three arguments version -- any types"
- alert(PolyFunc(1,"qq")); // "Number and string passed"
- alert(PolyFunc(function() {}, true)); // "Function and boolean passed"
- alert(PolyFunc(function() {}, 1)); // "Function and number passed"
- alert(PolyFunc(/a/, 1)); // "RegExp and something else passed"
- 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 раз. Для функций, которые сами по себе быстрые и вызываются очень часто, используйте осторожно.