Мне часто приходится сталкиваться с JavaScript-кодом, ошибки в котором вызваны неправильным понимаем того, как работают функции в JavaScript (кстати, значительная часть такого кода была написана мной самим). JavaScript — язык мультипарадигменный, и в нем имеются механизмы функционального программирования. Пора изучить эти возможности. В этой статье я расскажу вам о пяти способах вызова функций в JavaScript.
На первых этапах изучения JavaScript новички обычно думают, что функции в нем работают примерно так же, как, скажем, в C#. Но механизмы вызова функций в JavaScript имеют ряд важных отличий, и незнание их может вылиться в ошибки, которые будет непросто найти.
Давайте напишем простую функцию, которая возвращает массив из трех элементов — текущего значения
Новички часто объявляют функции так, как показано в примере выше. Вызвать эту функцию не составляет труда:
Погодите. Откуда взялся объект
В JavaScript, неважно, выполняется ли скрипт в браузере или в ином окружении, всегда определен глобальный объект. Любой код в нашем скрипте, не «привязанный» к чему-либо (т.е. находящийся вне объявления объекта) на самом деле находится в контексте глобального объекта. В нашем случае,
То есть вызов
Меня печалит тот факт, что этот способ вызова функций наиболее распространен, ведь он подразумевает наличие глобальной функции. А мы все знаем, что глобальные функции и переменные — не самый хороший тон в программировании. Особенно это справедливо для JavaScript. Избегайте глобальных определений, и не пожалеете.
Правило вызова функций №1: Если функция вызывается напрямую, без указания объекта (например,
Давайте создадим простой объект и сделаем
Видите разницу? Значение
Правило вызова функций №2: В функции, вызванной с использованием синтаксиса вызова метода, например,
Непонимание этого простого, в общем-то, принципа часто приводит к ошибкам при обработке событий:
Щелчок по первой кнопке покажет сообщение «btn1», потому что в данном случае мы вызываем функцию как метод, и
При использовании библиотек вроде jQuery думать об этом не надо. jQuery позаботится о том, чтобы переписать значение
Каким образом jQuery удается изменить значение
Еще два способа:
Логично, что чем чаще вы используете функции, тем чаще вам приходится передавать их и вызывать в разных контекстах. Зачастую возникает необходимость переопределить значение
Эти два метода очень похожи. Первый параметр переопределяет
Правило вызова функций №3: Если требуется переопределить значение
Я не буду подробно останавливаться на объявлении собственных типов в JavaScript, но считаю необходимым напомнить, что в JavaScript нет классов, а любой пользовательский тип нуждается в конструкторе. Кроме того, методы пользовательского типа лучше объявлять через
Важным в этом примере является наличие оператора
В остальном, код внутри конструктора, скорее всего, будет похож на код, который вы написали бы на другом языке. Значение
Правило вызова функций №4: При вызове функции с оператором
Надеюсь, понимание разницы между разными способами вызова функций возволит вам улучшить ваш JavaScript-код. Иногда непросто отловить ошибки, связанные со значением
На первых этапах изучения JavaScript новички обычно думают, что функции в нем работают примерно так же, как, скажем, в C#. Но механизмы вызова функций в JavaScript имеют ряд важных отличий, и незнание их может вылиться в ошибки, которые будет непросто найти.
Давайте напишем простую функцию, которая возвращает массив из трех элементов — текущего значения
this
и двух аргументов, переданных в функцию.function makeArray(arg1, arg2){
return [ this, arg1, arg2 ];
}
Самый распространенный способ: глобальный вызов
Новички часто объявляют функции так, как показано в примере выше. Вызвать эту функцию не составляет труда:
makeArray('one', 'two'); // => [ window, 'one', 'two' ]
Погодите. Откуда взялся объект
window
? Почему это у нас this
равен window
?В JavaScript, неважно, выполняется ли скрипт в браузере или в ином окружении, всегда определен глобальный объект. Любой код в нашем скрипте, не «привязанный» к чему-либо (т.е. находящийся вне объявления объекта) на самом деле находится в контексте глобального объекта. В нашем случае,
makeArray
— не просто функция, «гуляющая» сама по себе. На самом деле, makeArray
— метод глобального объекта (в случае исполнения кода в браузере) window
. Доказать это легко:alert( typeof window.methodThatDoesntExist ); // => undefined
alert( typeof window.makeArray ); // => function
То есть вызов
makeArray('one', 'two');
равносилен вызову window.makeArray('one', 'two');
.Меня печалит тот факт, что этот способ вызова функций наиболее распространен, ведь он подразумевает наличие глобальной функции. А мы все знаем, что глобальные функции и переменные — не самый хороший тон в программировании. Особенно это справедливо для JavaScript. Избегайте глобальных определений, и не пожалеете.
Правило вызова функций №1: Если функция вызывается напрямую, без указания объекта (например,
myFunction()
), значением this
будет глобальный объект (window
в случае исполнения кода в браузере).Вызов метода
Давайте создадим простой объект и сделаем
makeArray
его методом. Объект объявим с помощью литеральной нотации, а после вызовем наш метод:// создаем объект
var arrayMaker = {
someProperty: 'какое-то значение',
make: makeArray
};
// вызываем метод make()
arrayMaker.make('one', 'two'); // => [ arrayMaker, 'one', 'two' ]
// альтернативный синтаксис, используем квадратные скобки
arrayMaker['make']('one', 'two'); // => [ arrayMaker, 'one', 'two' ]
Видите разницу? Значение
this
в этом случае — сам объект. Почему не window
, как в предыдущем случае, ведь объявление функции не изменилось? Весь секрет в том, как передаются функции в JavaScript. Function
— это стандартный тип JavaScript, являющийся на самом деле объектом, и как и любой другой объект, функции можно передавать и копировать. В данном случае, мы как бы скопировали всю функцию, включая список аргументов и тело, и присвоили получившийся объект свойству make
объекта arrayMaker
. Это равносильно такому объявлению:var arrayMaker = {
someProperty: 'Какое-то значение';
make: function (arg1, arg2) {
return [ this, arg1, arg2];
}
};
Правило вызова функций №2: В функции, вызванной с использованием синтаксиса вызова метода, например,
obj.myFunction()
или obj['myFunction']()
, this
будет иметь значение obj
.Непонимание этого простого, в общем-то, принципа часто приводит к ошибкам при обработке событий:
<input type="button" value="Button 1" id="btn1" />
<input type="button" value="Button 2" id="btn2" />
<input type="button" value="Button 3" id="btn3" onclick="buttonClicked();" />
<script type="text/javascript">
function buttonClicked(){
var text = (this === window) ? 'window' : this.id;
alert( text );
}
var button1 = document.getElementById('btn1');
var button2 = document.getElementById('btn2');
button1.onclick = buttonClicked;
button2.onclick = function(){ buttonClicked(); };
</script>
Щелчок по первой кнопке покажет сообщение «btn1», потому что в данном случае мы вызываем функцию как метод, и
this
внутри функции получит значение объекта, которому этот метод принадлежит. Щелчок по второй кнопке выдаст «window», потому что в этом случае мы вызываем buttonClicked
напрямую (т.е. не как obj.buttonClicked()
). То же самое происходит, когда мы назначаем обработчик события в тэге элемента, как в случае третьей кнопки. Щелчок по третьей кнопке покажет то же самое сообщение, что и для второй.При использовании библиотек вроде jQuery думать об этом не надо. jQuery позаботится о том, чтобы переписать значение
this
в обработчике события так, чтобы значением this
был элемент, вызвавший событие:// используем jQuery
$('#btn1').click( function() {
alert( this.id ); // jQuery позаботится о том, чтобы 'this' являлась кнопкой
});
Каким образом jQuery удается изменить значение
this
? Читайте ниже.Еще два способа: apply()
и call()
Логично, что чем чаще вы используете функции, тем чаще вам приходится передавать их и вызывать в разных контекстах. Зачастую возникает необходимость переопределить значение
this
. Если вы помните, функции в JavaScript являются объектами. На практике это означает, что у функций есть предопределенные методы. apply()
и call()
— два из них. Они позволяют переопределять значение this
:var car = { year: 2008, model: 'Dodge Bailout' };
makeArray.apply( car, [ 'one', 'two' ] ); // => [ car, 'one', 'two' ]
makeArray.call( car, 'one', 'two' ); // => [ car, 'one', 'two' ]
Эти два метода очень похожи. Первый параметр переопределяет
this
. Различия между ними заключаются в последющих аргументах: Function.apply()
принимает массив значений, которые будут переданы функции, а Function.call()
принимает аргументы раздельно. На практике, по моему мнению, удобнее применять apply()
.Правило вызова функций №3: Если требуется переопределить значение
this
, не копируя функцию в другой объект, можно использовать myFunction.apply( obj )
или myFunction.call( obj )
.Конструкторы
Я не буду подробно останавливаться на объявлении собственных типов в JavaScript, но считаю необходимым напомнить, что в JavaScript нет классов, а любой пользовательский тип нуждается в конструкторе. Кроме того, методы пользовательского типа лучше объявлять через
prototype
, который является свойством фукции-конструктора. Давайте создадим свой тип:// объявляем конструктор
function ArrayMaker(arg1, arg2) {
this.someProperty = 'неважно';
this.theArray = [ this, arg1, arg2 ];
}
// объявляем методы
ArrayMaker.prototype = {
someMethod: function () {
alert('Вызван someMethod');
},
getArray: function () {
return this.theArray;
}
};
var am = new ArrayMaker( 'one', 'two' );
var other = new ArrayMaker( 'first', 'second' );
am.getArray(); // => [ am, 'one', 'two' ]
Важным в этом примере является наличие оператора
new
перед вызовом функции. Если бы не он, это был бы глобальный вызов, и создаваемые в конструкторе свойства относились бы к глобальному объекту. Нам такого не надо. Кроме того, в конструкторах обычно не возвращают значения явно. Без оператора new
конструктор вернул бы undefined
, с ним он возвращает this
. Хорошим стилем считается наименование конструкторов с заглавной буквы; это позволит вспомнить о необходимости оператора new
.В остальном, код внутри конструктора, скорее всего, будет похож на код, который вы написали бы на другом языке. Значение
this
в данном случае — это новый объект, который вы создаете.Правило вызова функций №4: При вызове функции с оператором
new
, значением this
будет новый объект, созданный средой исполнения JavaScript. Если эта функция не возвращает какой-либо объект явно, будет неявно возвращен this
.Заключение
Надеюсь, понимание разницы между разными способами вызова функций возволит вам улучшить ваш JavaScript-код. Иногда непросто отловить ошибки, связанные со значением
this
, поэтому имеет смысл предупреждать их возникновение заранее.