Lisp? Scheme? Erlang, Haskell? Они забыты! Наиболее широко известный язык функционального программирования это Javascript. JavaScript маскируется под процедурный язык до тех пор пока вы не будете готовы перейти на новый уровень понимания.
Процедурные функции
В начале были функции…
function hello(who) { alert('Hello '+who); // Выведет "Hello world" } hello('world');
Почти каждый язык программирования имеет конструкцию подобную этой. Каждый программист в мире знаком с этим синтаксисом. "hello" это имя нашей функции, но что произойдет, если мы создадим helloтак же, как и переменную?
Функции как переменные
Это может быть немного незнакомым синтаксисом, но большинству программистов должно быть комфортно работать с функцией, которая определена как переменная.
var hello = function (who) { alert('Hello '+who); // вызовет окошко с надписью "Hello world" } hello('world');
Тут нет большой разницы, просто добавьте слово var, знак равенства и сдвиньте hello, все остальное остается точно также. Мы создаем разными путями одну и туже функцию. Взгляните на это определение еще раз. Это больше, чем просто визуальное “удобство” определения функции. Эта конструкция на самом деле что-то значит. А значит она то, что наша функция будет следовать тем же правилам и способностям, как и любая другая переменная в JavaScript.
Она будет иметь те же возможности и может быть передана как параметр функциям, как и любая другая переменная.
Передача функций функциям
Если мы объявляем “hello” как переменную, то мы можем передать ее как параметр для функции.
var say = function(what) { what('say function'); } var hello = function (who) { alert('Hello '+who); // выведет "Hello say function" } say(hello);
Таким образом функции в JavaScript могут быть переданы как массив, объект или любая другая переменная. Иначе говоря все что может быть передано в функцию как параметр может содержать функцию.
Есть два способа использования функции в качестве аргумента. Вы можете передать указатель на саму функцию, или вы можете выполнить функцию, которая сама вернет функцию. В приведенном выше примере передается указатель “hello” в переменную “what” в функции “say”. “what” стала указателем на“hello”.
Если вы когда-либо имели дело с событиями в JavaScript, то вы вероятно уже использовали концепцию “функция как указатель”, потому что все обработчики событий требуют указатель на функцию, которая вызывается, когда они срабатывают. Например:
function doSomething() { alert("Вы нажали на кнопку мыши!"); } document.onclick=doSomething;
Этот пример “вешает” обработчик “doSomething” на событие onclick. Потому что мы используем лишь имя функции а не ее вызов. С другой стороны если бы “doSomething()” тоже возвращала функцию то все по прежнему бы работало хорошо.
function pushedMyButton() { alert("Вы нажали на кнопку мыши!"); } function doSomething() { return pushedMyButton; // вернет указатель на pushedMyButton } document.onclick=doSomething();
Анонимные функции
Скорее всего, если вы когда-либо писали стандартный Ajax или обработчик событий в JavaScript, вы знакомы с анонимной функции. Например:
document.onmousedown = function() { alert("Вы нажали кнопку мыши"); }
Это создаст обработчик события нажатия кнопки мыши. При нажатии кнопки произойдет вызов этой функции, она является анонимной, потому что мы не объявляем ее имя, и не можем вызвать ее позже иначе чем через onmousedown .
Это приводит нас к другой концепции функционального программирования - функция гораздо ближе к данным, чем мы могли предположить. Для того чтобы научится правильно пользоваться функциями в JavaScript их нужно воспринимать как обычные данные(число, строка, массив)
Само-вызов функции
Посмотрите на следующий код
var x = (function () {return(5+6)})(); document.writeln(typeof(x)); // Результат: Number document.writeln(x); // Результат: 11;
Обратите внимание что "x" будет содержать не анонимную функцию, а результат ее работы. Анонимная функция существовала ровно столько чтобы передать результат своей работы в переменную “x”.
Вот более реальный пример:
( function () { var s=document.createElement("script"); s.src="http://mydomain.com/script.js"; document.body.appendChild(s); } ) ();
Функция создает элемент script на странице и загружает в него сторонний сценарий. При этом сама функция не существует после своего выполнения и не занимает память. Это очень удобно, к примеру, когда функция нужна всего один раз и нет желания придумывать для нее название.
Вы также можете использовать само-вызов анонимной функции когда вам нужно сделать обработку каких-либо данных прежде чем подать их в функцию.
function doAlert(msg) { alert(msg); } doAlert( (function(animal1,animal2) { return animal1 + ' loves ' + animal2; } ) ('cat', 'dog') );
В этом примере, вызывается обычный doAlert, но вызывается с использованием анонимной само-вызывающейся функцией в качестве параметров. Разумеется таким образом вызывать можно не только анонимные функции:
function doDisplay(msg) { document.writeln(msg); } function buildString(num1, num2) { return num1 + ' + ' + num2 + ' = ' + (num1+num2); } for (i=0; i<10; i++) { doDisplay( (buildString) (i, i+5) ); }
Что эквивалентно традиционному вызову функций в других языках программирования
function doDisplay(msg) { document.writeln(msg); } function buildString(num1, num2) { return num1 + ' + ' + num2 + ' = ' + (num1+num2); } for (i=0; i<10; i++) { doDisplay( buildString(i, i+5) );
Забегая вперед скажу что заключение любого кода в функцию, ограничивает область видимости ее переменных, что очень удобно для совместного использования различных библиотек(jQuery, Prototype).
(function($){ $(body).find('div.block').show(); // ограничили $ внутри функции и работаем с jQuey })(jQuery) $('idblock').innerHTML = 5; // а тут спокойно работаем с prototype
Большинство плагинов jQuery написаны подобным образом, что позволяет применять их максимально совместимо с другими фреймворками. Но разумеется ничего не мешает вам писать в том же стиле и на prototype.
Понимание указателя на функцию
При создании функции , она занимает блок памяти. И только она ссылается на этот блок в куче данных JavaScript. Когда вы передаете указатель на функцию вы передаете указатель на эту ячейку памяти, а не на название функции. Это важно понимать. Лучше всего это проиллюстрирует пример.
var originalFunction = function () { alert('hello world'); } var copiedFunction = originalFunction; var originalFunction = function () { alert('goodbye world'); }
Переменная copiedFunction это указатель на блок памяти в котором лежит функция originalFunction "hello world" . Когда мы позже изменяем оригинальную функцию на 'goodbyeworld' то copiedFunction возвращает указатель на оригинальную функцию "hello world".
Область видимости функции
Поскольку JavaScript функции относятся как к обычным переменным, они имеют такие же возможности и правила, как обычные переменные - которые, в JavaScript, могут быть немного причудливыми.
Функций, определенные в теге <script> являются глобальными, доступны для любой функции, объекта, массива, и под-функции независимо от их местоположения в коде. Переменные в JavaScript, имеют глобальную область видимости, с одной оговоркой - переменные, определенные внутри функции являются локальными к этой функции и глобальными к ее под-функциям или потомкам.
var globalFunction = function () { // глобальная функция, доступна везде alert('hello world'); } var containerFunction = function() { // глобальная функция, доступна везде var subFunction = function() { //приватная функция, доступна только для containerFunction или ее потомков . alert('Private'); globalFunction(); // Мы имеем доступ к глобальным функциям . } globalFunction(); subFunction() . } containerFunction(); subFunction(); // этот вызов невозможен потому что subfunction () существует только внутри containerFunction();
Предостережение: Если вы объявляете новую переменную без ключевого слова “var” то такая переменная является одним из свойств глобального объекта windowи доступна глобально.
var globalFunction = function () { // глобальная функция, доступна везде alert('hello world'); } var containerFunction = function() { // глобальная функция, доступна везде subFunction = function() { // глобальная везде (так как не использовалось var) alert("I'm Private"); globalFunction(); } globalFunction(); subFunction() // не вызовет ошибки. } containerFunction(); subFunction(); // также не вызовет ошибки
Кроме того функцию можно вызвать как window.subFunction();
Функция как Объект
Все функции в JavaScriptэто объекты. Массив, число и строка также являются объектами.
var myArray = []; myArray[1] = 1; myArray[2] = 2; myArray[3] = 3; myArray.one = 'one'; myArray.two = 'two'; myArray.three='three'; for (i=0; i < myArray.length; i++) { document.writeln(myArray[i]); }
myАггау является объектом, который имеет свойства и методы, общие для всех массивов и который можно расширить. Когда мы перебираем в цикле myArray мы получим только [1] [2] [3] (1,2,3), свойства ('one','two','three') не являются частью массива они лишь свойства его объекта. Чтобы не выйти за границы массива myArray мы использовали свойство .length чтобы найти количество элементов в нем.
Таким образом, массив в JavaScript существуют в двух уровнях. На вершине мы имеем сам массив, а ниже объект, который можно использовать для хранения данных в его свойствах и создавать любые методы, которыми мы хотим в дополнение к существующим методам и свойствам всех массивов (такие как .length , .sort, .concat и т.д.).
Функции в JavaScript, подобны массивам , на высшем уровне есть сама функция. Она делает то, что мы определили ей делать. Но под этим идет объект функции, этот объект имеет общие свойства и методы, и эти свойства и методы могут быть расширены нами, по мере необходимости.
Функция Объекты и методы
Каждая функция обладает следующими свойствами:
arguments - массив / объект, содержащий аргументы, передаваемые функции
arguments.length – Хранит число аргументов в массиве
arguments.callee - указатель на выполняемую функцию (позволяет использовать анонимные функции рекурсивно).
length - число ожидаемых аргументов функции myFunc (х, у) = 2.
constructor - указатель на функцию конструктор.
prototype - позволяет создавать прототипы функций для всех объектов.
Каждая функция имеет следующие методы:
apply - метод, который позволяет более просто передать аргументы функции(аргументы подаются виде массива, и при ‘том позволяет сменить глобальную область видимости функции т.е. позволяет вызывать функции в другом контексте).
call – тоже самое что и apply, только аргументы подаются по отдельности как и в обычную функцию.
toSource - возвращает исходный текст функции в виде строки.
toString - возвращает исходный текст функции в виде строки.
valueOf - возвращает исходный текст функции в виде строки.
Понимание аргументов функции
Если есть объявленная функция
var myFunc = function(a,b,c) { }
Вы можете вызывать функцию следующим образом ...
myFunc(1); myFunc(1,2); myFunc(1,2,3); myFunc(1,2,3,4,5,6,7,8,9,10);
Если была вызвана myFunc (1) то b и c будет типа ‘undefined’ и вы можете увидеть это несколькими способами.
var myFunc = function(a,b,c) { if (!a) {a=1}; b = b || 2; if (c==undefined) {c=3;} document.writeln(a+'< br >'+b+'< br >'+c+'< BR >'); } myFunc(1);
Как вы видите, ‘undefined’ является ложное условие, таким образом Вы можете использовать логические операторы, чтобы обнаружить, если аргумент не был установлен и исправить это. Третий способ проверки является самым безопасным для нахождения неопределенных аргументов, так как 1 и 2 некорректно сработают на значения false,’’ и 0.
.length и arguments.length свойства позволяют проверить на ожидаемое количество аргументов и количество аргументов, которое на самом деле прошло. Например:
var myFunc = function(a,b,c) { document.writeln('expected '+myFunc.length+' arguments.< BR >'); //Outputs: 3 document.writeln('got ' + arguments.length+ ' arguments.< BR >'); //Outputs: 1 } myFunc(1);
Как вы можете видеть, функция ожидает 3 аргумента, но получила только 1!
Использование массива arguments
В нашем примере выше a,b, и c представлены переменными, которые мы использовали, чтобы получить первые три аргумента. Если аргументов будет меньше чем ожидалось то переменные будут установлены как "undefined", если там будет больше чем ожидалось они будут по-прежнему доступны для нас в массиве arguments
var myFunc = function() { document.writeln(arguments.length+'< BR >'); // displays 10 for(i=0; i < arguments.length; i++) { document.writeln(arguments[i]+','); // displays: 1,2,3,4,5,6,7,8,9,10, } } myFunc(1,2,3,4,5,6,7,8,9,10);
Опыт показывает что массив arguments имеет не все свойства и методы доступные обычным массивам поэтому если вы хотите использовать методы (sort, splice, slice, и т.д.) то нужно сделать следующее:
var args = Array.prototype.slice.apply(arguments);
Как передать аргументы одной функции другой
Время от времени, кто-то спрашивает, как передать аргументы одной функции в качестве аргументов для другой. Например:
var otherFunc=function() { alert(arguments.length); // вернет 1 потому что как параметр ему подается лишь 1 массив } var myFunc = function() { alert(arguments.length); // вернет 10 otherFunc(arguments); // подаем аргументы единым массивом } myFunc(1,2,3,4,5,6,7,8,9,10);
Здесь подается 10 аргументов в myFunc и myFunc пытается передать их otherFunc но вместо этого передает его аргументы в виде массива, а не как список аргументов. Мы можем обойти это с вызовом метода apply, которая будет передавать массив аргументов как список аргументов в новую функцию
var otherFunc = function() { alert(arguments.length); // Вернет 10 } var myFunc = function() { alert(arguments.length); // Вернет: 10 otherFunc.apply(this, arguments); } myFunc(1,2,3,4,5,6,7,8,9,10);
Как использовать рекурсию на анонимных функциях
Допустим, у нас есть анонимные функции факториала, и мы хотим сделать это рекурсивно. Как мы вызовем функцию без имени? Хорошо что в JavaScript arguments.callee свойство содержит указатель на выполняемую в данный момент функцию, это означает, что анонимная функции может, действительно, вызывать сама себя.
alert((function(n){ if(n <= 1){return 1;}else{return n*arguments.callee(n-1);}})(10));
Заключение
Можно далеко уйти в объектно-ориентированное программирование и объект Function Javascript, но это тема для другой статьи. Сейчас, однако, я надеюсь, что эта статья помогла вам понять мощь и гибкость JavaScript функций, и позволит Вам использовать для своих целей программирование на JavaScript.
Комментарии
Спасибо! С Уважением Дмитрий.
Незаметно произрастают в молодой неопытной Душе ростки безразличия и чванства.
Сначала "ЗАБАВЛЯЕТ" грустный, голодный котёнок,
потом станет забавлять несчастная, голодная бабушка,
собирающая на помойке грязные буржуазные объедки,
ну, и, наконец, вообще обхохочешься от вида сожжённого на Вечном Огне бомжа...
Вот так всё и начинается… Но этим всё и заканчивается…