Россия, г. Санкт-Петербург |
Создание динамического наполнения страницы. Основы JavaScript
12.1.5.10. Функции
Вместе с объектами функции играют ключевую роль в JavaScript. Вот объявление самой простой функции:
function add(x, y) { var total = x + y; return total; }
В JavaScript функции могут принимать от 0 и больше именованных параметров. Тело функции может содержать любые выражения JavaScript и объявления внутренних переменных. Выражение return может быть использовано в любое время для прекращения выполнения функции и возврата значения. Если return отсутствует или не возвращает значения (пустой return ), функция возвращает undefined.
Именованные параметры условны, т.е. можно вызвать функцию без параметров или с лишними параметрами. В первом случае значения параметров будут установлены в undefined:
add() //результат – "NaN", т.к. undefined нельзя складывать
Также можно передать аргументов больше, чем ожидает функция:
add(2, 3, 4) //результат – "5", т.к. складывает первые два параметра, а 4 игнорируется
Внутри тела функции доступна дополнительная переменная "arguments". Это "массиво-подобный" объект, в котором хранятся все переменные, переданные в функцию. Перепишем предыдущую функцию, чтобы она суммировала все параметры, которые ей передают:
function add() { var sum = 0; for (var i = 0, j = arguments.length; i < j; i++) { sum += arguments[i]; } return sum; }
И вызовем ее:
add(2, 3, 4, 5) //результат – "14"
Напишем функцию для подсчета среднего арифметического:
function avg() { var sum = 0; for (var i = 0, j = arguments.length; i < j; i++) { sum += arguments[i]; } return sum / arguments.length; }
И вызовем ее:
avg(2, 3, 4, 5) //результат – "3.5"
Если мы захотим вычислить среднее по массиву, то можно переписать функцию, но можно воспользоваться уже существующей, поскольку в JavaScript функцию можно вызвать несколькими способами.
Первый это классический func(). Любую функцию можно вызвать также с помощью встроенного в Function метода apply(), который вызовет функцию с параметрами, переданными в виде массива в apply() вторым параметром:
avg.apply(null, [2, 3, 4, 5]) //результат – "3.5"
JavaScript позволяет создавать анонимные функции:
var avg = function() { var sum = 0; for (var i = 0, j = arguments.length; i < j; i++) { sum += arguments[i]; } return sum / arguments.length; }
JavaScript позволяет функциям делать рекурсивные вызовы (т.е. вызывать самих себя). Это очень полезно, когда имеешь дело с древовидными структурами, например, такими как DOM:
function countChars(elm) { if (elm.nodeType == 3) { return elm.nodeValue.length; } var count = 0; for (var i = 0, child; child = elm.childNodes[i]; i++) { count += countChars(child); } return count; }
12.1.5.11. Пользовательские объекты
В классических объектно-ориентированных языках объект – это коллекция данных и методов, оперирующих этими данными [5].
Рассмотрим объект person с полями first и last name (имя и фамилия). Предположим, надо иметь два метода для различных отображений полного имени: как "first last" или как "last, first". Это можно сделать с использованием объекта и функций следующим образом:
function makePerson(first, last) { return { first: first, last: last } } function personFullName(person) { return person.first + ' ' + person.last; } function personFullNameReversed(person) { return person.last + ', ' + person.first }
Тогда при вызове этих функций:
s = makePerson("Иван", "Петров"); personFullName(s) //результат – "Иван Петров" personFullNameReversed(s) //результат – "Петров, Иван"
Но если необходимо как-то присоединять имена функций к объектам, то, как это делается в обычных объектно-ориентированных языках программирования, то можно написать следующий код:
function makePerson(first, last) { return { first: first, last: last, fullName: function() { return this.first + ' ' + this.last; }, fullNameReversed: function() { return this.last + ', ' + this.first; } } }
Тогда при вызове этих функций:
s = makePerson("Иван", "Петров") s.fullName()//результат – "Иван Петров" s.fullNameReversed()//результат – "Петров, Иван"
Здесь встречается ключевое словом "this". При использовании внутри функции "this" ссылается на текущий объект. Чему будет равно "this" на самом деле зависит от того как была вызвана функция. Если функция будет вызвана через точечную нотацию или квадратные скобки, тогда "this" указывает на этот объект. Если просто вызвать функцию, то "this" будет указывать на глобальный объект. Непонимание этого может привести к неприятным последствиям:
s = makePerson("Иван", "Петров") var fullName = s.fullName; fullName()//результат – "undefined undefined"
Когда вызывается fullName(), в роли "this" выступает глобальный объект. И поскольку не определены глобальные переменные first и last, для каждой из них будет получено undefined.
Можно использовать "this" для усовершенствования нашей функции makePerson:
function Person(first, last) { this.first = first; this.last = last; this.fullName = function() { return this.first + ' ' + this.last; } this.fullNameReversed = function() { return this.last + ', ' + this.first; } } var s = new Person("Иван", "Петров");
Здесь присутствует ключевое слово "new". "new" связан с "this". Он создает пустой объект и вызывает указанную функцию, внутри которой "this" указывает на этот новый объект. Функции, которые будут использоваться вместе с "new" называются "конструкторами". Обычно название таких функций начинается с заглавной буквы, и напоминает о том, что функция должна быть использована вместе с "new".
Однако до сих пор остались некоторые проблемы: каждый раз при создании объекта, необходимо создавать по две новых функции внутри него. Разделим эти функции между всеми объектами:
function personFullName() { return this.first + ' ' + this.last; } function personFullNameReversed() { return this.last + ', ' + this.first; } function Person(first, last) { this.first = first; this.last = last; this.fullName = personFullName; this.fullNameReversed = personFullNameReversed; }
Теперь функции будут создаваться только один раз, а ссылки на них назначаются в конструкторе. Однако существует более хороший вариант:
function Person(first, last) { this.first = first; this.last = last; } Person.prototype.fullName = function() { return this.first + ' ' + this.last; } Person.prototype.fullNameReversed = function() { return this.last + ', ' + this.first; }
Person.prototype это объект общий для всех объектов, созданных с помощью Person. Он входит в "prototype chain" (цепочку поиска): каждый раз, когда происходит попытка получить свойство объекта Person, которое у него отсутствует, JavaScript проверяет, нет ли такого свойства у Person.prototype. Таким образом, все, что будет присвоено Person.prototype становится доступно всем объектам порожденным конструктором Person, в том числе и через объект this.
Это необычайно мощное средство. В JavaScript можно модифицировать prototype (прототипы) объектов в любое время и в любом месте программы, что означает, что можно добавлять методы к объектам в процессе выполнения программы:
s = new Person("Иван", "Петров"); s.firstNameCaps(); //результат – "TypeError on line 1: s.firstNameCaps is not a function" Person.prototype.firstNameCaps = function() { return this.first.toUpperCase() } s.firstNameCaps() //результат – "ИВАН"
Как уже упоминалось, прототип является частью цепочки, в конце которой находится Object.prototype, у которого есть метод toString() – он вызывается, когда объект приводится к строке. И его можно переопределить для отладки:
var s = new Person("Иван", "Петров"); s //результат – "[object Object]" Person.prototype.toString = function() { return '<ФИО: ' + this.fullName() + '>'; } s //результат – "<ФИО: Иван Петров>"
Теперь вернемся к рассмотрению первого параметр метода avg.apply().Первый параметр apply() это объект, на который будет ссылаться ключевое слово "this", если оно используется в теле вызываемой функции. Например, можно сделать тривиальную реализацию "new":
function trivialNew(constructor) { var o = {}; // создаем объект constructor.apply(o, arguments); return o; }
Это конечно не совсем "new", так как не создается прототипная цепочка ( prototype chain ).
Для apply() есть похожая функция call, которая также позволяет установить "this", но вместо массива параметров принимает параметры через запятую (как при обычном вызове):
function lastNameCaps() { return this.last.toUpperCase(); } var s = new Person("Иван", "Петров"); lastNameCaps.call(s); // Тоже самое, что и: s.lastNameCaps = lastNameCaps; s.lastNameCaps();
12.1.5.12. Внутренние функции
В JavaScript функции можно объявлять внутри других функций. Очень важным аспектом является то, что внутренние функции имеют доступ к локальным переменным, определенным в родительской функции:
function betterExampleNeeded() { var a = 1; function oneMoreThanA() { return a + 1; } return oneMoreThanA(); }
Это позволяет не засорять глобальный контекст ( scope ) именами функций, которые нужны для решения частных проблем в одной части кода. А также разделять переменные между разными вложенными функциями, не пользуясь глобальным пространством имен.