Как вызвать функцию в javascript. Объявление и вызов функции. Вложенные области видимости

  • Перевод

Мне часто приходится сталкиваться с JavaScript-кодом, ошибки в котором вызваны неправильным понимаем того, как работают функции в JavaScript (кстати, значительная часть такого кода была написана мной самим). JavaScript - язык мультипарадигменный, и в нем имеются механизмы функционального программирования. Пора изучить эти возможности. В этой статье я расскажу вам о пяти способах вызова функций в 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 .

Непонимание этого простого, в общем-то, принципа часто приводит к ошибкам при обработке событий:
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(); };
Щелчок по первой кнопке покажет сообщение «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 , поэтому имеет смысл предупреждать их возникновение заранее.

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

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

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

Функции - объекты

В 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 . Возможны неожиданные пересечения с другими переменными окна, конфликты и глюки.

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

Заданная через 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 .

При первом запуске функция работает с аргументами 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 , который содержит

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

    Function func() { for(var i=0;i alert(3)

    Пример передачи функции по ссылке

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

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

    Var map = function(func, arr) { var result = for(var i=0; i element. Every time we want to do that, we can just invoke the function with this

    rather than having to write all that code out again every time we want to repeat it. And functions can contain whatever code you like - you can even call other functions from inside functions. The above function for example calls the random() function three times, which is defined by the following code:

    Function random(number) { return Math.floor(Math.random()*number); }

    We needed this function because the browser"s built-in Math.random() function only generates a random decimal number between 0 and 1. We wanted a random whole number between 0 and a specified number.

    Invoking functions

    You are probably clear on this by now, but just in case ... to actually use a function after it has been defined, you"ve got to run - or invoke - it. This is done by including the name of the function in the code somewhere, followed by parentheses.

    Function myFunction() { alert("hello"); } myFunction() // calls the function once

    Anonymous functions

    You may see functions defined and invoked in slightly different ways. So far we have just created a function like so:

    Function myFunction() { alert("hello"); }

    But you can also create a function that doesn"t have a name:

    Function() { alert("hello"); }

    This is called an anonymous function - it has no name! It also won"t do anything on its own. You generally use an anonymous function along with an event handler, for example the following would run the code inside the function whenever the associated button is clicked:

    Var myButton = document.querySelector("button"); myButton.onclick = function() { alert("hello"); }

    The above example would require there to be a element available on the page to select and click. You"ve already seen this structure a few times throughout the course, and you"ll learn more about and see it in use in the next article.

    You can also assign an anonymous function to be the value of a variable, for example:

    Var myGreeting = function() { alert("hello"); }

    This function could now be invoked using:

    MyGreeting();

    This effectively gives the function a name; you can also assign the function to be the value of multiple variables, for example:

    Var anotherGreeting = function() { alert("hello"); }

    This function could now be invoked using either of

    MyGreeting(); anotherGreeting();

    But this would just be confusing, so don"t do it! When creating functions, it is better to just stick to this form:

    Function myGreeting() { alert("hello"); }

    You will mainly use anonymous functions to just run a load of code in response to an event firing - like a button being clicked - using an event handler. Again, this looks something like this:

    MyButton.onclick = function() { alert("hello"); // I can put as much code // inside here as I want }

    Function parameters

    Some functions require parameters to be specified when you are invoking them - these are values that need to be included inside the function parentheses, which it needs to do its job properly.

    Note : Parameters are sometimes called arguments, properties, or even attributes.

    As an example, the browser"s built-in Math.random() function doesn"t require any parameters. When called, it always returns a random number between 0 and 1:

    Var myNumber = Math.random();

    The browser"s built-in string replace() function however needs two parameters - the substring to find in the main string, and the substring to replace that string with:

    Var myText = "I am a string"; var newString = myText.replace("string", "sausage");

    Note : When you need to specify multiple parameters, they are separated by commas.

    It should also be noted that sometimes parameters are optional - you don"t have to specify them. If you don"t, the function will generally adopt some kind of default behavior. As an example, the array join() function"s parameter is optional:

    Var myArray = ["I", "love", "chocolate", "frogs"]; var madeAString = myArray.join(" "); // returns "I love chocolate frogs" var madeAString = myArray.join(); // returns "I,love,chocolate,frogs"

    If no parameter is included to specify a joining/delimiting character, a comma is used by default.

    Function scope and conflicts

    Let"s talk a bit about scope - a very important concept when dealing with functions. When you create a function, the variables and other things defined inside the function are inside their own separate scope , meaning that they are locked away in their own separate compartments, unreachable from inside other functions or from code outside the functions.

    The top level outside all your functions is called the global scope . Values defined in the global scope are accessible from everywhere in the code.

    JavaScript is set up like this for various reasons - but mainly because of security and organization. Sometimes you don"t want variables to be accessible from everywhere in the code - external scripts that you call in from elsewhere could start to mess with your code and cause problems because they happen to be using the same variable names as other parts of the code, causing conflicts. This might be done maliciously, or just by accident.

    For example, say you have an HTML file that is calling in two external JavaScript files, and both of them have a variable and a function defined that use the same name:

    greeting(); // first.js var name = "Chris"; function greeting() { alert("Hello " + name + ": welcome to our company."); } // second.js var name = "Zaptec"; function greeting() { alert("Our company is called " + name + "."); }

    Both functions you want to call are called greeting() , but you can only ever access the second.js file"s greeting() function - it is applied to the HTML later on in the source code, so its variable and function overwrite the ones in first.js .

    Keeping parts of your code locked away in functions avoids such problems, and is considered best practice.

    It is a bit like a zoo. The lions, zebras, tigers, and penguins are kept in their own enclosures, and only have access to the things inside their enclosures - in the same manner as the function scopes. If they were able to get into other enclosures, problems would occur. At best, different animals would feel really uncomfortable inside unfamiliar habitats - a lion or tiger would feel terrible inside the penguins" watery, icy domain. At worst, the lions and tigers might try to eat the penguins!

    The zoo keeper is like the global scope - he or she has the keys to access every enclosure, to restock food, tend to sick animals, etc.

    Active learning: Playing with scope

    Let"s look at a real example to demonstrate scoping.

  • First, make a local copy of our function-scope.html example. This contains two functions called a() and b() , and three variables - x , y , and z - two of which are defined inside the functions, and one in the global scope. It also contains a third function called output() , which takes a single parameter and outputs it in a paragraph on the page.
  • Open the example up in a browser and in your text editor.
  • Open the JavaScript console in your browser developer tools. In the JavaScript console, enter the following command: output(x); You should see the value of variable x output to the screen.
  • Now try entering the following in your console output(y); output(z); Both of these should return an error along the lines of "ReferenceError: y is not defined ". Why is that? Because of function scope - y and z are locked inside the a() and b() functions, so output() can"t access them when called from the global scope.
  • However, what about when it"s called from inside another function? Try editing a() and b() so they look like this: function a() { var y = 2; output(y); } function b() { var z = 3; output(z); } Save the code and reload it in your browser, then try calling the a() and b() functions from the JavaScript console: a(); b(); You should see the y and z values output in the page. This works fine, as the output() function is being called inside the other functions - in the same scope as the variables it is printing are defined in, in each case. output() itself is available from anywhere, as it is defined in the global scope.
  • Now try updating your code like this: function a() { var y = 2; output(x); } function b() { var z = 3; output(x); } Save and reload again, and try this again in your JavaScript console:
  • a(); b(); Both the a() and b() call should output the value of x - 1. These work fine because even though the output() calls are not in the same scope as x is defined in, x is a global variable so is available inside all code, everywhere.
  • Finally, try updating your code like this: function a() { var y = 2; output(z); } function b() { var z = 3; output(y); } Save and reload again, and try this again in your JavaScript console:
  • a(); b(); This time the a() and b() calls will both return that annoying "