Javascript.RU

Функции

В этой статье описаны функции Javascript на уровне языка: создание, параметры, приемы работы, замыкания и многое другое.

Существует 3 способа создать функцию. Основное отличие в результате их работы - в том, что именованная функция видна везде, а анонимная - только после объявления:

Именованные (FunctionDeclaration) Анонимные (FunctionExpression)
function имя(параметры) {
...
}
var имя = function(параметры) {

}
...
var имя = new Function(параметры, '...')
Именованные функции доступны везде в области видимости Анонимные - доступны только с момента объявления. Синтаксис new Function используется редко, в основном для получения функции из текста, например, динамически загруженного с сервера в процессе выполнения скриптов.
/* функция sum 
определена ниже 
*/
var a = sum(2,2)

function sum(x,y) {
	return x+y
}
/* будет ошибка, 
т.к sum еще не существует
*/
var a = sum(2,2)

var sum = function(x,y) {
	return x+y
}

В javascript функции являются полноценными объектами встроенного класса Function. Именно поэтому их можно присваивать переменным, передавать и, конечно, у них есть свойства:

function f() {
	...
}
f.test = 6
...
alert(f.test) // 6

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

Например,

function func() {
	var funcObj = arguments.callee

	funcObj.test++
	alert(funcObj.test)
}
func.test = 1
func()
func()

В начале работы каждая функция создает внутри себя переменную arguments и присваивает arguments.callee ссылку на себя. Так что arguments.callee.test - свойство func.test, т.е статическая переменная test.

В примере нельзя было сделать присвоение:

var test = arguments.callee.test
test++

так как при этом операция ++ сработала бы на локальной переменной test, а не на свойстве test объекта функции.

Объект arguments также содержит все аргументы и может быть преобразован в массив (хотя им не является), об этом - ниже, в разделе про параметры.

Каждая функция, точнее даже каждый запуск функции задает свою индивидуальную область видимости.

Переменные можно объявлять в любом месте. Ключевое слово var задает переменную в текущей области видимости. Если его забыть, то переменная попадет в глобальный объект window. Возможны неожиданные пересечения с другими переменными окна, конфликты и глюки.

javascript function

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

javascript function

Заданная через var переменная видна везде в области видимости, даже до оператора var. Для примера сделаем функцию, которая будет менять переменную, var для которой находится ниже.

Например:

function a() {
	z = 5 // поменяет z локально.. 

	// .. т.к z объявлена через var
	var z
}

// тест
delete z // очистим на всякий случай глобальную z
a()

alert(window.z)  // => undefined, т.к z была изменена локально

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

Если функции передано меньше параметров, чем есть в определении, то отсутствующие считаются undefined.

Следующая функция возвращает время time, необходимое на преодоление дистанции distance с равномерной скоростью speed.

mwsnap016.jpg

При первом запуске функция работает с аргументами distance=10, speed=undefined. Обычно такая ситуация, если она поддерживается функцией, предусматривает значение по умолчанию:

// если speed - ложное значение(undefined, 0, false...) - подставить 10
speed = speed || 10

Оператор || в яваскрипт возвращает не true/false, а само значение (первое, которое приводится к true).

Поэтому его используют для задания значений по умолчанию. В нашем вызове speed будет вычислено как undefined || 10 = 10.

Поэтому результат будет 10/10 = 1.

Второй запуск - стандартный.

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

Ну и в последнем случае аргументов вообще нет, поэтому distance = undefined, и имеем результат деления undefined/10 = NaN (Not-A-Number, произошла ошибка).

Непосредственно перед входом в тело функции, автоматически создается объект arguments, который содержит

  1. Аргументы вызова, начиная от нуля
  2. Длину в свойстве length
  3. Ссылку на саму функцию в свойстве callee

Например,

function func() {
    for(var i=0;i<arguments.length;i++) {
        alert("arguments["+i+"] = "+arguments[i])
    }
}
func('a','b',true)

// выведет
// arguments[0] = a
// arguments[1] = b
// arguments[2] = true

Свойство arguments похоже на массив, т.к у него есть длина и числовые индексы. На самом деле arguments не принадлежит классу Array и не содержит его методов, таких как push, pop и других.

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

var args = Array.prototype.slice.call(arguments)
// .. теперь args - настоящий массив аргументов ..
args.shift()
...

Вызвать функцию для массива аргументов можно при помощи apply:

var func = function(a,b) { alert(a+b) }
var arr = [1,2]
func.apply(null, arr)  // => alert(3)

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

Например, map берет функцию func, применяет ее к каждому элементу массива arr и возвращает получившийся массив:

var map = function(func, arr) {
    var result = [ ]
    for(var i=0; i<arr.length; i++) {
        result[i] = func(arr[i])
    }
    return result
}

Пример использования:

map(run, [10, 20, 30])  // = [1,2,3]

Или можно создать анонимную функцию непосредственно в вызове map:

// анонимная функция утраивает числа
map( function (a) { return a*3 } ,  [1,2,3])  // = [3,6,9]

Бывают функции, аргументы которых сильно варьируются.

Например:

// можно указать только часть аргументов
// не указанные - вычисляются или берутся по умолчанию
function resize(toWidth, toHeight,  saveProportions, animate)  {
	// значения по умолчанию
	saveProportions = saveProportions || true
	animate = animate || true
	toHeight = toHeight || ...
}

Вызов с необязательными параметрами приходится делать так:

resize(100, null, null, true)

Чтобы избежать лишних null и сделать код более понятным, используют нечто вроде "keyword arguments", существующих в Python и Ruby. Для этого много параметров пакуют в единый объект:

function resize(setup)  {
	// значения по умолчанию
	var saveProportions = setup.saveProportions || true
	var animate = setup.animate || true
	var toHeight = setup.toHeight || ...
}

Вызов теперь делается гораздо проще:

var setup = {toWidth: 100, animate: true}

resize(setup)
// или
resize({toWidth: 100, animate: true})

Так - куда понятнее. А если параметров больше 5, то вообще - единственный нормальный способ.

Кроме того, с объектом можно удобнее делать последовательности вызовов вроде:

var setup = {toWidth: 100, animate: true, saveProportions: false}

resize(setup)

setup.toWidth = 200
resize(setup)

Автор: Андрей Параничев, дата: 6 июля, 2008 - 19:05
#permalink

Конечно мне всё описанное в статье уже известно, но материал очень полезный.
Жаль, что в то время, когда я только начинал изучать программирование и JavaScript, не было таких статей...


Автор: Marantz (не зарегистрирован), дата: 20 июля, 2008 - 22:02
#permalink

Очень внятно и полезно - СПАСИБО!


Автор: Dmitry A. Soshnikov, дата: 22 июля, 2008 - 22:35
#permalink

Да, статьи у тебя, Илья, как всегда, полезны. Доходчиво написано, особенно для новичков.

Есть маленькое уточнение:

> Именованные (FunctionDeclaration)

не каждая именованная функция является declaration'ом:

function testFunctionExpression() {
  
  // функция test, хоть и имеет имя,
  // все же является FunctionExpression
  // (в скобках может быть только expression)
  (function test() {});

  // соответственно, она не попала в скоп (точнее - в variable object)
  // т.к. FunctionExpression'ы не affect'ят на него
  alert(test); // даже не undefined, а падает по ecxeption'у

}

Автор: Shock (не зарегистрирован), дата: 24 июля, 2008 - 15:42
#permalink

Спасибо огромное! Очень долго искал вот это для своей JS библиотеки:

function resize(setup)  {
    // значения по умолчанию
    var saveProportions = setup.saveProportions || true
    var animate = setup.animate || true
    var toHeight = setup.toHeight || ...
}
var setup = {toWidth: 100, animate: true}
 
resize(setup)
// или
resize({toWidth: 100, animate: true})

Автор: Ляпин Дмитрий (не зарегистрирован), дата: 6 августа, 2008 - 11:52
#permalink

Отличная статья


Автор: Герман Клюшин (не зарегистрирован), дата: 21 ноября, 2008 - 11:47
#permalink

А как все таки задавать значения параметров по умолчанию?
Только уже в самом теле функции?


Автор: Илья Кантор, дата: 21 ноября, 2008 - 14:49
#permalink

Да, только в теле функции


Автор: Игорь нео (не зарегистрирован), дата: 17 декабря, 2008 - 07:12
#permalink

Блин, а я как не разбиралась в Яве, так и не разбираюсь...Надо учиться


Автор: Александр Макаров (не зарегистрирован), дата: 9 февраля, 2009 - 07:18
#permalink

да!!! нужная вещь)))


Автор: Гость2 (не зарегистрирован), дата: 12 марта, 2009 - 14:17
#permalink

Касательно :

// если speed - ложное значение(undefined, 0, false...) - подставить 10
speed = speed || 10

Я верю,что так и будет, но не понимаю здесь преобразование типов. Я так понимаю, что В правой части speed должно преобразовываться к Boolean и если он true, то второй опреанда 10 не вычисляется, но при этом и левой части должно присвоиться значение типа Boolean, а не того типа , которое имеет speed на входе в функцию. В чем я ошибаюсь и почему ?


Автор: Илья Кантор, дата: 12 марта, 2009 - 16:59
#permalink

Оператор || в яваскрипт возвращает не true/false, а само значение (первое, которое приводится к true).


Автор: kefi, дата: 13 марта, 2009 - 14:26
#permalink

Объекты , передаются в функцию по ссылке, а как передать в функцию по ссылке параметры примитивных типов (Undefined , Null, Boolean, String, Number ), чтобы можно было их изменить в функции ?


Автор: Илья Кантор, дата: 13 марта, 2009 - 23:57
#permalink

Никак. Элементарные значения не передаются по ссылке, только объекты, к которым также относятся массивы, даты и т.п.

Здесь javascript ведет себя так же, как, например, java/php5


Автор: kefi, дата: 23 апреля, 2009 - 21:20
#permalink

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


Автор: Леонид Евстигнеев, дата: 27 мая, 2009 - 15:31
#permalink

Насчёт заклинания:

var args = Array.prototype.slice.call(arguments)
// .. теперь args - настоящий массив аргументов ..
args.shift()
...

Хотелось бы иметь либо подробное разъяснение, либо ссылку на место где можно читать про соответствующую магию.
Я так полагаю второе предпочтительнее, т.к. это выходит за рамки статьи.


Автор: Mirash (не зарегистрирован), дата: 13 сентября, 2009 - 21:27
#permalink

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


Автор: Булат (не зарегистрирован), дата: 23 августа, 2009 - 11:49
#permalink

Вопрос по поводу проставления булевых значений по умолчанию в этом куске кода:

// можно указать только часть аргументов
// не указанные - вычисляются или берутся по умолчанию
function resize(toWidth, toHeight, saveProportions, animate) {
// значения по умолчанию
saveProportions = saveProportions || true
animate = animate || true
toHeight = toHeight || ...
}

Что будет если вызвать эту функцию со следующими параметрами?

resize(100, null, false, true)

Казалось бы, если учесть следующее утверждение:

// если speed - ложное значение(undefined, 0, false...) - подставить 10
speed = speed || 10

то значение параметра saveProportions будет вычислено как false || true == true, хотя в данном случае мы хотели, чтобы saveProportions равнялось false.

Я правильно понимаю?


Автор: Гость (не зарегистрирован), дата: 15 сентября, 2009 - 08:57
#permalink

Да, Ты понимаешь правильно
пиши так:
function myFunc(s)
{
if (typeof s == "undefined") s = "default value for s";
alert(s);
};


Автор: Гость (не зарегистрирован), дата: 15 декабря, 2009 - 20:18
#permalink

Не понятно про имена функций при вызове по ссылке. Мы можем сослатся на функцию вот так:

var myFunc = function(val)
{
     alert(val);
}

myFunc('Hello'); // выдаст Hello

Тоесть мы переменную myFunc фактически приравниваем к функции.
Но мочему тогда нельзя сделать вот так?

function myFunc(val)
{
    alert(val);
}

var someVar = "myFunc";

someVar('Hello');

Вовсяком случае у меня этот код не работает.
Как быть если имя нужной функции я вычисляю и результат помещаю в строковую переменную? Тоесть строковая переменная содержит имя нужной мне функции. При попытке вызова как напрямую, так и через someVar.call() / someVar.apply() - получаю ошибку. Не ужели только через eval(someVar+"()") ?


Автор: Гость (не зарегистрирован), дата: 16 декабря, 2009 - 01:27
#permalink

Привет. Присвоение значения переменной надо делать без кавычек:
var someVar = myFunc;


Автор: Гость (не зарегистрирован), дата: 16 декабря, 2009 - 01:43
#permalink

Спасибо за ответ, но.... :-)

Суть именно в том, что в кавычках вычесляется имя функции, которую нужно запустить. Кавычки тут не зря. К примеру:

function myFunc1(val){...}

function myFunc2(val){...}

var someVar = "myFunc" + 1; 
// или 
var someVar = "myFunc" + 2

;

Это просто пример, но суть думаю понятна - имя функции вычесляется динамично.
Как в таком случае можно запустить "вычесленную" функцию? Пока я использу eval(), но есть ли более красивое решение?


Автор: Гость (не зарегистрирован), дата: 16 декабря, 2009 - 13:24
#permalink

Только если записывать ф-ию в хеш (или объявлять ее глобально, что почти тоже самое: она попадет в объект window).


Автор: jack128 (не зарегистрирован), дата: 17 января, 2010 - 00:31
#permalink

пример к "Сворачивание параметров в объект" не очень хороший честно говоря.

вполне можно просто вызвать resize(100) и все оставшиеся аргументы возьмутся по умолчанию.
А сворачивать аргументы в объект имеет смысл если часто нуно изменять например 3 и пятый аргумент на отличные от умалчиваемых.


Отправить комментарий

Приветствуются комментарии:
  • Полезные.
  • Дополняющие прочитанное.
  • Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи.
    Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Содержание этого поля является приватным и не предназначено к показу.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Разрешены HTML-таги: <strike> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <u> <i> <b> <pre> <img> <abbr> <blockquote> <h1> <h2> <h3> <h4> <h5> <p> <div> <span> <sub> <sup>
  • Строки и параграфы переносятся автоматически.
  • Текстовые смайлы будут заменены на графические.

Подробнее о форматировании

CAPTCHA
Антиспам
12 + 7 =
Введите результат. Например, для 1+3, введите 4.
 
Текущий раздел
Поиск по сайту
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Статьи и мероприятия

Будьте в курсе наших последних новостей!

Ответьте, пожалуйста..
О чем бы вы хотели услышать на конференции по javascript?

На какие темы послушать доклады? Конференция состоится в середине мая.


Последние обсуждения на форуме
Forum
Последние комментарии