Глаза страшат, а руки делают

ЧАСТЬ ВТОРАЯ


25. Объект Function

  • именованные и анонимные функции;
  • возвращение функции;
  • локальные переменные в функции;
  • вызов функции;
  • функции и события;
  • аргументы;
  • свойства объекта Function.

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

Функция также является объектом, и этот объект можно создавать с помощью конструктора. Как всякий объект, Function можно назначать на переменную.

С этими способами мы сегодня познакомимся. Но это бывает нужно далеко не всегда. Чаще всего функция создаётся ключевым словом function.

Создание функции

Функцию можно определить как именованный набор инструкций.

Именованная функция

Самый распространённый способ создания функции — использование ключевого слова function и имени функции:

function имяФункции([аргумент1, аргумент2, ..., аргументN]) {
/* тело функции
(набор инструкций) */
[return возвращаемоеЗначение]
}

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

/* вызываем функцию sum */
var a = sum(2, 2)
/* а теперь объявляем её */
function sum(x, y) {
    return x + y
}

Анонимная функция

Можно при создании назначить функцию на переменную:

var имяПеременной = function([аргумент1, аргумент2, ..., аргументN]) {
/* тело функции
(набор инструкций) */
[return возвращаемоеЗначение]
}

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

/* вызываем функцию, которой не существует */
var a = sum(2, 2)
var sum = function(x, y) {
    return x + y
}

Анонимную функцию можно также создать с помощью конструктора new.

Обратите внимание

Здесь мы используем не ключевое слово function, а объект Function. Все встроенные объекты пишутся с большой буквы.

var имяПеременной = new Function([аргумент1, аргумент2, ..., аргументN]) {
/* тело функции
(набор инструкций) */
[return возвращаемоеЗначение]
}

или:

var имяПеременной = new Function("аргумент1", "аргумент2", ..., "аргументN", "return возвращаемоеЗначение")

Например:

var multiply = new Function(x, y) {
    return x * y
}

или:

var multiply = new Function("x", "y", "return x * y")

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

Синтаксис new Function используется редко, об этом мы подробнее поговорим в четвёртой части, а сейчас нам достаточно знать, что «и такое бывает».

Возвращение функции

Функция возвращает своё значение после прохождения всего цикла инструкций в её теле. Бывает, что из инструкций неясно, какое именно значение должно быть возвращено. Тогда используется инструкция return, которая явно определяет возвращаемое значение.

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

function anyCase(a, b) {
if (a.toLowerCase() > b.toLowerCase())
  return 1; // возвращает значение первого оператора
if (a.toLowerCase() < b.toLowerCase())
  return -1; // возвращает значение первой ветви второго оператора
else
  return 0; // возвращает последнее значение
}

Переменные в функции

Функция «понимает» все глобальные переменные объявленные за её пределами. Но если эти переменные участвуют в инструкциях скрипта помимо функции и могут быть изменены этими инструкциями, то лучше использовать для функции локальные переменные, объявленные в её теле.

Внимание!

Для объявления локальных переменных ключевое слово var обязательно (необъявленная переменная попадёт в глобальный объект window; возможны неожиданные пересечения с другими глобальными переменными, конфликты и глюки).

Локальные переменные действуют только внутри функции. Это очень удобно для избежания конфликтов, и я бы рекомендовал пользоваться в функциях именно локальными переменными, за исключением особых случаев (пример вынесения значения функции в глобальную переменную можно найти в моей статье «Велик могучим русский языка, ч. 2»).

Вызов функции

Для того, чтобы функция сработала, её нужно вызвать.

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

<script type="text/javascript">
имяФункции()
</script>

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

var a;
document.write(a);
a = multiple(4,3);
function  multiple(x,y) {
    return x * y;
}

В результате получаем undefined (не определено). На самом деле функция честно сработала. Но уже потом, когда «поезд ушёл». В общем — «ребята, вы меня на экран не вызывали».

Вызов функции сработает в любом месте. Но результат вызова — только после вызова. Следующий код сработает корректно:

var a;
a = multiple(4,3);
document.write(a);
function  multiple(x,y) {
    return x * y;
}

Возможно, этот пример показался вам слишком наивным. Но учтите, что в нём только «три сосны», а реальные скрипты порой напоминают джунгли. А правила в них те же самые и, в общем-то, весьма простые. Поэтому не мешает лишний раз призвать к вниманию в мелочах.

Функции и фреймы

Любая функция принадлежит окну, в котором она определена. Если она определена в автономном файле.js, то она будет принадлежать тем страницам, в которых есть ссылки на этот файл.

Если же нужно обратиться к функции, находящейся в родственном фрейме, ссылка должна включать не только имя функции, но и имя фрейма:

parent.имяФрейма.имяФункции()

Функции и события

В большинстве случаев функции вызывают какое-нибудь действие. А за действия, как мы знаем, отвечают события. И часто бывает нужно, чтобы функция сработала по нажатию кнопки, ссылки или ещё чего-нибудь. Тогда вызов нужно поместить в атрибут соответствующего события внутри тэга.

<form>
<input type="button" onClick="имяФункции(параметрАргумента1,
параметрАргумента2);">
</form>

<a href="javascript: имяФункции(параметрАргумента1,параметрАргумента2);">

Если надо, чтобы функция сработала (то есть была вызвана) сразу при загрузке страницы, то её вызов нужно вставить в событие onLoad тэга body:

<body onLoad="имяФункции(параметрАргумента1,параметрАргумента2);">

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

function move() {
   var myrem = eval("document.getElementById('cnt')");
   myrem.style.top = document.body.scrollTop + 100;
   myrem.style.left = document.body.scrollLeft + 100;
}

window.onresize = move;
window.onscroll = move;
window.onсlick = move;

(пример из моей статьи «Сноски в тексте»)

Взаимодействие с элементами HTML

Когда функция ссылается на элемент HTML по его id, то вызов должен осуществляться либо после этого элемента в коде, либо через событие onLoad тэга body.

Например, мы хотим покрасить текст элемента в красный цвет.

<!-- не сработает -->
<head>
<script type="text/javascript">
function red(a) {
var lnk = eval(document.getElementById(a))
lnk.style.color = "#FF0000"
}
red("red")
</script>
</head>
<body>
<h2 id="red">КРАСНЫЙ</h2>
</body>

<!-- не сработает -->
<head>
<script type="text/javascript">
function red(a) {
var lnk = eval(document.getElementById(a))
lnk.style.color = "#FF0000"
}
</script>
</head>
<body>
<script type="text/javascript">
red("red")
</script>
<h2 id="red">КРАСНЫЙ</h2>
</body>

<!-- сработает -->
<head>
<script type="text/javascript">
function red(a) {
var lnk = eval(document.getElementById(a))
lnk.style.color = "#FF0000"
}
</script>
</head>
<body>
<h2 id="red">КРАСНЫЙ</h2>
<script type="text/javascript">
red("red")
</script>
</body>

<!-- сработает -->
<head>
<script type="text/javascript">
function red(a) {
var lnk = eval(document.getElementById(a))
lnk.style.color = "#FF0000"
}
</script>
</head>
<body onLoad="red('red')">
<h2 id="red">КРАСНЫЙ</h2>
</body>

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

<!-- это работает! -->
<head>
<script type="text/javascript">
function red(a) {
var lnk = eval(document.getElementById(a))
lnk.style.color = "#FF0000"
}
setTimeout("red('red')", 0)
</script>
</head>
<body>
<h2 id="red">КРАСНЫЙ</h2>
</body>

Аргументы функции

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

Аргумент функции — это тоже своего рода переменная. В объявлении функции в качестве аргумента используется имя этой переменной. А в вызове функции на это место подставляется конкретный параметр (аналогично присвоению значения переменной).

Аргументов может быть несколько, и они записываются внутри круглых скобок через запятые.

Вспомним функцию из 3-го урока, которая показывала или скрывала текстовый блок. В качестве аргумента использована переменная nnn. В теле функции используется инструкция, содержащая document.getElementById(nnn). В данном случае nnn замещает id элемента HTML. А в вызове функции для конкретного элемента нужно подставить вместо имени аргумента конкретный id.

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

<script type="text/javascript">
function displ(nnn) {
if (document.getElementById(nnn).style.display == 'none')
{document.getElementById(nnn).style.display = 'block'}
 else {document.getElementById(nnn).style.display = 'none'}
}
</script>

<a href="javascript: displ('raz')"><i>Вызов функции для блока "raz"</i></a>
<div id="raz" style="display: none;">Текст в скрытом блоке "raz"</div>

<a href="javascript: displ('dva')"><i>Вызов функции для блока "dva"</i></a>
<div id="dva" style="display: none;">Текст в скрытом блоке "dva"</div>

Ключевое слово this

В 21 и 24 уроках мы создали собственные методы для объектов Array и Date. В первом случае у функции был один аргумент, во втором — аргументов не было. Ключевое слово this оба раза являлось «псевдонимом» того объекта, на который функция была назначена в качестве метода. Если бы такая функция работала сама по себе (то есть как обычная функция, а не «болванка» для метода), то на месте this должен был быть аргумент функции. Поясню на схеме:

/* СОЗДАНИЕ МЕТОДА */
Object.prototype.myMethod = myFunction;
function myFunction() {
/* инструкции для this */
}
/* вызов метода */
Object.myMethod()

/* СОЗДАНИЕ ОБЫЧНОЙ ФУНКЦИИ */
function myFunction(arg) {
/* инструкции для arg */
}
/* вызов функции */
myFunction(Object)

То есть, чтобы функцию, требующую в качестве аргумента некий объект, сделать методом этого объекта, этот аргумент нужно убрать из скобок, а в теле превратить его в слово this. Если функции требуются второй, третий и т.д. аргументы, то они станут первым, вторым и т.д. аргументами метода:

/* ПРОСТО ФУНКЦИЯ */
function myFunction(arg1, arg2, arg3) {
/* инструкции */
}
/* вызов функции */
myFunction(Object, param2, param3)

/* ЭТА ЖЕ ФУНКЦИЯ КАК МЕТОД */
Object.prototype.myMethod = myFunction;
/* arg2 и arg3 превратились в arg1 и arg2 */
function myFunction(arg1, arg2,) {
/* инструкции;
бывший arg1 теперь называется this */
}
/* вызов метода */
Object.myMethod(param1, param2)

В одном из ближайших уроках мы построим такую функцию-метод.

Функция как объект

Иногда бывает нужна функция, которая могла бы принимать по обстоятельствам разное число аргументов. Это можно устроить. А чтобы понять, как это работает, рассмотрим функцию как объект.

Как и у других объектов, у функции есть свойства и методы.

Свойство arguments

Это свойство подобно массиву, его элементами являются аргументы функции. Оно имеет несколько собственных свойств: arguments.callee, arguments.caller, arguments.length.

Свойства arguments.callee (ссылка на содержание функции) и arguments.caller (ссылка на имя функции) считаются устаревшими, и их можно обойти другими способами. Сегодня мы их затрагивать не будем.

arguments.length

Как и у массива, у arguments есть свойство length (длина, т. е. количество аргументов).

Стучали падуанские часы,
и педантично Страсбургcкие били,
и четко нас на четверти дробили
Милана мелодичные часы.

Ю. Левитанский

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

<head>
<script type="text/javascript">
function setClock() {
var container = new Date();
var i;
var hh = container.getHours();
var mm = container.getMinutes();
var ss = container.getSeconds();
if (hh < 10) {hh = "0" + hh}
if (mm < 10) {mm = "0" + mm}
if (ss < 10) {ss = "0" + ss}
var myTimer = hh + ":" + mm + ":" + ss;
/* Назначаем значение на каждый аргумент, сколько бы их ни было */ for (i=0; i<arguments.length; i++) document.getElementById(arguments[i]).innerHTML = myTimer;
} setInterval("setClock('showTimer1','showTimer2','showTimer3')", 1000) </script> </head> <body> <h3 align="center" id="showTimer1"></h3> <h2 align="center" id="showTimer2"></h2> <p align="center" id="showTimer3"></p> </body>

Результат:

Примечание

этот код использует метод setInterval и тоже «заводится» из любого места, как и с методом setTimeout.

Ну и ещё, я не удержался и немного их подкрасил («подклассил»). Кстати, если использовать для дизайна дополнительные тэги <span>, <b> и т.п., то id нужно присваивать именно им, а не абзацу или заголовку.

А вот пример чуть похитрее. Если в функцию введён один числовой аргумент, она показывает его как длину в метрах, если два — как площадь в квадратных метрах, три — объём в кубических метрах. Во всех иных случаях, а также при нечисловых аргументах, она сообщает, что введены «неверные данные».

function calc() {
var i, res = 1, err = "Неверные данные";
if (arguments.length>0 && arguments.length<=3){
    switch (arguments.length) {
    case 1:
        var m = " м"
        break
    case 2:
        var m = " м<sup>2</sup>"
        break
    case 3:
        var m = " м<sup>3</sup>"
        break}
	for (i=0; i<arguments.length; i++)
    {res = res * arguments[i]}
        if (isNaN(res))
            return err;
        else
            return res+m;
    }
    else
    return err;
}

Теперь протестируем функцию:

document.write(calc(30,20) + "<br>")
document.write(calc("тра","ля","ля") + "<br>")
document.write(calc(4,5,3,8) + "<br>")
document.write(calc(4,5,3) + "<br>")
document.write(calc() + "<br>")
document.write(calc(32))

И посмотрим, что получилось:

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

Домашнее задание

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

Некоторые вводные

Аргументы: день, месяц

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

При нужных аргументах выводит поздравление — например,

<h3 align='center' style='color: F00;'>Поздравляю с Днём рождения!<br>Счастья, здоровья, успехов во всём!</h3>

В остальных случаях — пустая строка.

Шпаргалок на сей раз не даю. Хотя... всё зависит от вашей смекалки.


Итак, мы узнали:

как работать с функциями.


К следующему уроку >>


 014426